Skip to content

Fix #948 make component registry immutable #981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions src/Autofac/Builder/DeferredCallback.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Linq;
using Autofac.Core;
using Autofac.Core.Registration;

namespace Autofac.Builder
{
Expand All @@ -9,19 +8,19 @@ namespace Autofac.Builder
/// </summary>
public class DeferredCallback
{
private Action<IComponentRegistry> _callback;
private Action<IComponentRegistryBuilder> _callback;

/// <summary>
/// Initializes a new instance of the <see cref="DeferredCallback"/> class.
/// </summary>
/// <param name="callback">
/// An <see cref="Action{T}"/> that executes a registration action
/// against an <see cref="IComponentRegistry"/>.
/// against an <see cref="IComponentRegistryBuilder"/>.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// Thrown if <paramref name="callback" /> is <see langword="null" />.
/// </exception>
public DeferredCallback(Action<IComponentRegistry> callback)
public DeferredCallback(Action<IComponentRegistryBuilder> callback)
{
if (callback == null)
{
Expand All @@ -37,12 +36,12 @@ public DeferredCallback(Action<IComponentRegistry> callback)
/// </summary>
/// <value>
/// An <see cref="Action{T}"/> that executes a registration action
/// against an <see cref="IComponentRegistry"/>.
/// against an <see cref="IComponentRegistryBuilder"/>.
/// </value>
/// <exception cref="System.ArgumentNullException">
/// Thrown if <paramref name="value" /> is <see langword="null" />.
/// </exception>
public Action<IComponentRegistry> Callback
public Action<IComponentRegistryBuilder> Callback
{
get
{
Expand Down
2 changes: 2 additions & 0 deletions src/Autofac/Builder/MetadataKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,7 @@ internal static class MetadataKeys
internal const string ContainerBuildOptions = "__ContainerBuildOptions";

internal const string RegisteredPropertyKey = "__RegisteredKey";

internal const string RegistrationSourceAddedPropertyKey = "__RegistrationSourceAddedKey";
}
}
2 changes: 1 addition & 1 deletion src/Autofac/Builder/RegistrationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ public static IComponentRegistration CreateRegistration(
/// <param name="cr">Component registry to make registration in.</param>
/// <param name="builder">Registration builder with data for new registration.</param>
public static void RegisterSingleComponent<TLimit, TActivatorData, TSingleRegistrationStyle>(
IComponentRegistry cr,
IComponentRegistryBuilder cr,
IRegistrationBuilder<TLimit, TActivatorData, TSingleRegistrationStyle> builder)
where TSingleRegistrationStyle : SingleRegistrationStyle
where TActivatorData : IConcreteActivatorData
Expand Down
9 changes: 5 additions & 4 deletions src/Autofac/Builder/StartableManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Autofac.Core;
Expand All @@ -10,15 +10,16 @@ internal static class StartableManager
/// <summary>
/// Executes the startable and auto-activate components in a context.
/// </summary>
/// <param name="properties">The set of properties used during component registration.</param>
/// <param name="componentContext">
/// The <see cref="IComponentContext"/> in which startables should execute.
/// </param>
internal static void StartStartableComponents(IComponentContext componentContext)
internal static void StartStartableComponents(IDictionary<string, object> properties, IComponentContext componentContext)
{
var componentRegistry = componentContext.ComponentRegistry;
try
{
componentRegistry.Properties[MetadataKeys.StartOnActivatePropertyKey] = true;
properties[MetadataKeys.StartOnActivatePropertyKey] = true;

// We track which registrations have already been auto-activated by adding
// a metadata value. If the value is present, we won't re-activate. This helps
Expand Down Expand Up @@ -53,7 +54,7 @@ internal static void StartStartableComponents(IComponentContext componentContext
}
finally
{
componentRegistry.Properties.Remove(MetadataKeys.StartOnActivatePropertyKey);
properties.Remove(MetadataKeys.StartOnActivatePropertyKey);
}
}
}
Expand Down
104 changes: 39 additions & 65 deletions src/Autofac/ContainerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Autofac.Builder;
using Autofac.Core;
using Autofac.Core.Registration;
using Autofac.Features.Collections;
using Autofac.Features.GeneratedFactories;
using Autofac.Features.Indexed;
Expand Down Expand Up @@ -80,15 +78,41 @@ public ContainerBuilder()
/// </summary>
/// <param name="properties">The properties used during component registration.</param>
internal ContainerBuilder(IDictionary<string, object> properties)
: this(properties, new ComponentRegistryBuilder(new DefaultRegisteredServicesTracker(), properties))
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ContainerBuilder"/> class.
/// </summary>
/// <param name="componentRegistryBuilder">The builder to use for building the underlying <see cref="IComponentRegistry" />.</param>
internal ContainerBuilder(IComponentRegistryBuilder componentRegistryBuilder)
: this(new Dictionary<string, object>(), componentRegistryBuilder)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ContainerBuilder"/> class.
/// </summary>
/// <param name="properties">The properties used during component registration.</param>
/// <param name="componentRegistryBuilder">The builder to use for building the underlying <see cref="IComponentRegistry" />.</param>
internal ContainerBuilder(IDictionary<string, object> properties, IComponentRegistryBuilder componentRegistryBuilder)
{
Properties = properties;

if (!Properties.ContainsKey(BuildCallbackPropertyKey))
{
Properties.Add(BuildCallbackPropertyKey, new List<Action<IContainer>>());
}

ComponentRegistryBuilder = componentRegistryBuilder;
}

/// <summary>
/// Gets the builder to use for building the underlying <see cref="IComponentRegistry" />.
/// </summary>
public IComponentRegistryBuilder ComponentRegistryBuilder { get; }

/// <summary>
/// Gets the set of properties used during component registration.
/// </summary>
Expand All @@ -103,7 +127,7 @@ internal ContainerBuilder(IDictionary<string, object> properties)
/// </summary>
/// <remarks>This is primarily for extending the builder syntax.</remarks>
/// <param name="configurationCallback">Callback to execute.</param>
public virtual DeferredCallback RegisterCallback(Action<IComponentRegistry> configurationCallback)
public DeferredCallback RegisterCallback(Action<IComponentRegistryBuilder> configurationCallback)
{
if (configurationCallback == null) throw new ArgumentNullException(nameof(configurationCallback));

Expand Down Expand Up @@ -141,12 +165,16 @@ public ContainerBuilder RegisterBuildCallback(Action<IContainer> buildCallback)
/// <returns>A new container with the configured component registrations.</returns>
public IContainer Build(ContainerBuildOptions options = ContainerBuildOptions.None)
{
var result = new Container(Properties);
result.ComponentRegistry.Properties[MetadataKeys.ContainerBuildOptions] = options;
Build(result.ComponentRegistry, (options & ContainerBuildOptions.ExcludeDefaultModules) != ContainerBuildOptions.None);
Properties[MetadataKeys.ContainerBuildOptions] = options;
ComponentRegistryBuilder.Register(new SelfComponentRegistration());

Build(ComponentRegistryBuilder, (options & ContainerBuildOptions.ExcludeDefaultModules) != ContainerBuildOptions.None);

var componentRegistry = ComponentRegistryBuilder.Build();

var result = new Container(componentRegistry);
if ((options & ContainerBuildOptions.IgnoreStartableComponents) == ContainerBuildOptions.None)
StartableManager.StartStartableComponents(result);
StartableManager.StartStartableComponents(Properties, result);

var buildCallbacks = GetBuildCallbacks();
foreach (var buildCallback in buildCallbacks)
Expand All @@ -155,60 +183,6 @@ public IContainer Build(ContainerBuildOptions options = ContainerBuildOptions.No
return result;
}

/// <summary>
/// Configure an existing container with the component registrations
/// that have been made.
/// </summary>
/// <remarks>
/// Update can only be called once per <see cref="ContainerBuilder"/>
/// - this prevents ownership issues for provided instances.
/// </remarks>
/// <param name="container">An existing container to make the registrations in.</param>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "You can't update any arbitrary context, only containers.")]
[Obsolete("Containers should generally be considered immutable. Register all of your dependencies before building/resolving. If you need to change the contents of a container, you technically should rebuild the container. This method may be removed in a future major release.")]
public void Update(IContainer container)
{
Update(container, ContainerBuildOptions.None);
}

/// <summary>
/// Configure an existing container with the component registrations
/// that have been made and allows additional build options to be specified.
/// </summary>
/// <remarks>
/// Update can only be called once per <see cref="ContainerBuilder"/>
/// - this prevents ownership issues for provided instances.
/// </remarks>
/// <param name="container">An existing container to make the registrations in.</param>
/// <param name="options">Options that influence the way the container is updated.</param>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "You can't update any arbitrary context, only containers.")]
[Obsolete("Containers should generally be considered immutable. Register all of your dependencies before building/resolving. If you need to change the contents of a container, you technically should rebuild the container. This method may be removed in a future major release.")]
public void Update(IContainer container, ContainerBuildOptions options)
{
// Issue #462: The ContainerBuildOptions parameter is added here as an overload
// rather than an optional parameter to avoid method binding issues. In version
// 4.0 or later we should refactor this to be an optional parameter.
if (container == null) throw new ArgumentNullException(nameof(container));
Update(container.ComponentRegistry);
if ((options & ContainerBuildOptions.IgnoreStartableComponents) == ContainerBuildOptions.None)
StartableManager.StartStartableComponents(container);
}

/// <summary>
/// Configure an existing registry with the component registrations
/// that have been made.
/// </summary>
/// <remarks>
/// Update can only be called once per <see cref="ContainerBuilder"/>
/// - this prevents ownership issues for provided instances.
/// </remarks>
/// <param name="componentRegistry">An existing registry to make the registrations in.</param>
[Obsolete("Containers should generally be considered immutable. Register all of your dependencies before building/resolving. If you need to change the contents of a container, you technically should rebuild the container. This method may be removed in a future major release.")]
public void Update(IComponentRegistry componentRegistry)
{
this.UpdateRegistry(componentRegistry);
}

/// <summary>
/// Configure an existing registry with the component registrations
/// that have been made. Primarily useful in dynamically adding registrations
Expand All @@ -219,13 +193,13 @@ public void Update(IComponentRegistry componentRegistry)
/// - this prevents ownership issues for provided instances.
/// </remarks>
/// <param name="componentRegistry">An existing registry to make the registrations in.</param>
internal void UpdateRegistry(IComponentRegistry componentRegistry)
internal void UpdateRegistry(IComponentRegistryBuilder componentRegistry)
{
if (componentRegistry == null) throw new ArgumentNullException(nameof(componentRegistry));
Build(componentRegistry, true);
}

private void Build(IComponentRegistry componentRegistry, bool excludeDefaultModules)
private void Build(IComponentRegistryBuilder componentRegistry, bool excludeDefaultModules)
{
if (componentRegistry == null) throw new ArgumentNullException(nameof(componentRegistry));

Expand All @@ -241,7 +215,7 @@ private void Build(IComponentRegistry componentRegistry, bool excludeDefaultModu
callback.Callback(componentRegistry);
}

private void RegisterDefaultAdapters(IComponentRegistry componentRegistry)
private void RegisterDefaultAdapters(IComponentRegistryBuilder componentRegistry)
{
this.RegisterGeneric(typeof(KeyedServiceIndex<,>)).As(typeof(IIndex<,>)).InstancePerLifetimeScope();
componentRegistry.AddRegistrationSource(new CollectionRegistrationSource());
Expand Down
15 changes: 8 additions & 7 deletions src/Autofac/Core/ComponentRegisteredEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

using System;

using Autofac.Core.Registration;

namespace Autofac.Core
{
/// <summary>
Expand All @@ -34,9 +36,9 @@ namespace Autofac.Core
public class ComponentRegisteredEventArgs : EventArgs
{
/// <summary>
/// Gets the container into which the registration was made.
/// Gets the <see cref="IComponentRegistryBuilder" /> into which the registration was made.
/// </summary>
public IComponentRegistry ComponentRegistry { get; }
public IComponentRegistryBuilder ComponentRegistryBuilder { get; }

/// <summary>
/// Gets the component registration.
Expand All @@ -46,15 +48,14 @@ public class ComponentRegisteredEventArgs : EventArgs
/// <summary>
/// Initializes a new instance of the <see cref="ComponentRegisteredEventArgs"/> class.
/// </summary>
/// <param name="registry">The container into which the registration
/// was made.</param>
/// <param name="registryBuilder">The <see cref="IComponentRegistryBuilder" /> into which the registration was made.</param>
/// <param name="componentRegistration">The component registration.</param>
public ComponentRegisteredEventArgs(IComponentRegistry registry, IComponentRegistration componentRegistration)
public ComponentRegisteredEventArgs(IComponentRegistryBuilder registryBuilder, IComponentRegistration componentRegistration)
{
if (registry == null) throw new ArgumentNullException(nameof(registry));
if (registryBuilder == null) throw new ArgumentNullException(nameof(registryBuilder));
if (componentRegistration == null) throw new ArgumentNullException(nameof(componentRegistration));

ComponentRegistry = registry;
ComponentRegistryBuilder = registryBuilder;
ComponentRegistration = componentRegistration;
}
}
Expand Down
18 changes: 4 additions & 14 deletions src/Autofac/Core/Container.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Autofac.Core.Activators.Delegate;

using Autofac.Core.Lifetime;
using Autofac.Core.Registration;
using Autofac.Core.Resolving;
Expand All @@ -45,20 +45,10 @@ public class Container : Disposable, IContainer, IServiceProvider
/// <summary>
/// Initializes a new instance of the <see cref="Container"/> class.
/// </summary>
/// <param name="properties">The properties used during component registration.</param>
internal Container(IDictionary<string, object> properties = null)
/// <param name="componentRegistry">The registry of components.</param>
internal Container(IComponentRegistry componentRegistry)
{
ComponentRegistry = new ComponentRegistry(properties ?? new Dictionary<string, object>());

ComponentRegistry.Register(new ComponentRegistration(
LifetimeScope.SelfRegistrationId,
new DelegateActivator(typeof(LifetimeScope), (c, p) => { throw new InvalidOperationException(ContainerResources.SelfRegistrationCannotBeActivated); }),
new CurrentScopeLifetime(),
InstanceSharing.Shared,
InstanceOwnership.ExternallyOwned,
new Service[] { new TypedService(typeof(ILifetimeScope)), new TypedService(typeof(IComponentContext)) },
new Dictionary<string, object>()));

ComponentRegistry = componentRegistry;
_rootLifetimeScope = new LifetimeScope(ComponentRegistry);
}

Expand Down
Loading