Skip to content
Open
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
126 changes: 126 additions & 0 deletions src/Autofac/Builder/DecoratorRegistrationBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright (c) Autofac Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using Autofac.Core;
using Autofac.Core.Registration;
using Autofac.Core.Resolving.Pipeline;
using Autofac.Features.Decorators;

namespace Autofac.Builder;

/// <summary>
/// Default implementation of <see cref="IDecoratorRegistrationBuilder{TService}"/>.
/// </summary>
/// <typeparam name="TService">The service type being decorated.</typeparam>
/// <remarks>
/// The component registration backing a decorator is not created until the container is built.
/// That is what allows configuration (pipeline middleware, metadata, the decoration condition) to
/// be applied after the <c>RegisterDecorator</c> call returns; <see cref="Register"/> snapshots
/// everything at build time.
/// </remarks>
internal sealed class DecoratorRegistrationBuilder<TService> : IDecoratorRegistrationBuilder<TService>
{
private readonly Type _serviceType;
private readonly RegistrationData _registrationData;
private readonly IResolvePipelineBuilder _pipelineBuilder;
private readonly Func<DecoratorService, IComponentRegistration> _registrationFactory;
private Func<IDecoratorContext, bool>? _condition;

/// <summary>
/// Initializes a new instance of the <see cref="DecoratorRegistrationBuilder{TService}"/> class.
/// </summary>
/// <param name="serviceType">The service type being decorated.</param>
/// <param name="registrationData">The registration data of the decorator registration.</param>
/// <param name="pipelineBuilder">The resolve pipeline builder of the decorator registration.</param>
/// <param name="registrationFactory">
/// A factory that, given the decorator service, produces the decorator's component registration.
/// This is invoked once, at container build time.
/// </param>
/// <param name="condition">The initial decoration condition, if any.</param>
internal DecoratorRegistrationBuilder(
Type serviceType,
RegistrationData registrationData,
IResolvePipelineBuilder pipelineBuilder,
Func<DecoratorService, IComponentRegistration> registrationFactory,
Func<IDecoratorContext, bool>? condition)
{
_serviceType = serviceType;
_registrationData = registrationData;
_pipelineBuilder = pipelineBuilder;
_registrationFactory = registrationFactory;
_condition = condition;
}

/// <inheritdoc/>
public IDictionary<string, object?> Metadata => _registrationData.Metadata;

/// <inheritdoc/>
public IDecoratorRegistrationBuilder<TService> ConfigurePipeline(Action<IResolvePipelineBuilder> configurationAction)
{
if (configurationAction is null)
{
throw new ArgumentNullException(nameof(configurationAction));
}

configurationAction(_pipelineBuilder);

return this;
}

/// <inheritdoc/>
public IDecoratorRegistrationBuilder<TService> WithMetadata(string key, object? value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}

_registrationData.Metadata.Add(key, value);

return this;
}

/// <inheritdoc/>
public IDecoratorRegistrationBuilder<TService> WithMetadata(IEnumerable<KeyValuePair<string, object?>> properties)
{
if (properties == null)
{
throw new ArgumentNullException(nameof(properties));
}

foreach (var prop in properties)
{
WithMetadata(prop.Key, prop.Value);
}

return this;
}

/// <inheritdoc/>
public IDecoratorRegistrationBuilder<TService> WithCondition(Func<IDecoratorContext, bool> condition)
{
_condition = condition ?? throw new ArgumentNullException(nameof(condition));

return this;
}

/// <summary>
/// Creates the decorator registration and adds it, along with the decorator middleware, to the
/// component registry. Invoked from the container builder's configuration callbacks.
/// </summary>
/// <param name="registryBuilder">The component registry builder.</param>
internal void Register(IComponentRegistryBuilder registryBuilder)
{
var decoratorService = new DecoratorService(_serviceType, _condition);
var decoratorRegistration = _registrationFactory(decoratorService);
var middleware = new DecoratorMiddleware(decoratorService, decoratorRegistration);

// Not using the ContainerBuilder extension methods here: they queue additional
// configuration callbacks, and this code is itself running from inside one.
registryBuilder.AddServiceMiddlewareSource(
new ServiceWithTypeMiddlewareSource(_serviceType, middleware, MiddlewareInsertionMode.StartOfPhase));

// Add the decorator to the registry so the pipeline gets built.
registryBuilder.Register(decoratorRegistration);
}
}
93 changes: 93 additions & 0 deletions src/Autofac/Builder/IDecoratorRegistrationBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Autofac Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using Autofac.Core.Resolving.Pipeline;
using Autofac.Features.Decorators;

namespace Autofac.Builder;

/// <summary>
/// Allows a decorator registration to be configured after it has been added to a
/// <see cref="ContainerBuilder"/>.
/// </summary>
/// <typeparam name="TService">The service type being decorated.</typeparam>
/// <remarks>
/// <para>
/// A decorator is not an ordinary registration. The service it exposes is fixed by the
/// decoration machinery, and its lifetime is governed by the registration being decorated.
/// This builder therefore exposes a deliberately smaller surface than
/// <see cref="IRegistrationBuilder{TLimit, TActivatorData, TRegistrationStyle}"/>: there is no
/// <c>As</c>, <c>Named</c>, <c>Keyed</c>, or lifetime configuration, because none of those are
/// meaningful for a decorator.
/// </para>
/// <para>
/// The underlying component registration is not created until the container is built, so
/// configuration may be applied at any point before <see cref="ContainerBuilder.Build(ContainerBuildOptions)"/>.
/// </para>
/// </remarks>
public interface IDecoratorRegistrationBuilder<TService>
{
/// <summary>
/// Gets the metadata associated with the decorator registration.
/// </summary>
/// <remarks>
/// Integrations that need to read as well as write metadata (for example, to append to a
/// list of values already present) can use this dictionary directly; otherwise prefer
/// <see cref="WithMetadata(string, object?)"/>.
/// </remarks>
IDictionary<string, object?> Metadata
{
get;
}

/// <summary>
/// Configure the resolve pipeline of the decorator registration.
/// </summary>
/// <param name="configurationAction">
/// An action that can add middleware to the decorator's registration pipeline.
/// </param>
/// <returns>The builder, to allow continued configuration.</returns>
/// <remarks>
/// Middleware added here runs when the decorator instance itself is activated, which means
/// it wraps the decorator rather than the component the decorator decorates.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="configurationAction" /> is <see langword="null" />.
/// </exception>
IDecoratorRegistrationBuilder<TService> ConfigurePipeline(Action<IResolvePipelineBuilder> configurationAction);

/// <summary>
/// Provide a key/value pair of metadata associated with the decorator registration.
/// </summary>
/// <param name="key">Key of the metadata item.</param>
/// <param name="value">Value of the metadata item.</param>
/// <returns>The builder, to allow continued configuration.</returns>
IDecoratorRegistrationBuilder<TService> WithMetadata(string key, object? value);

/// <summary>
/// Provide a set of metadata values associated with the decorator registration.
/// </summary>
/// <param name="properties">The metadata values to add.</param>
/// <returns>The builder, to allow continued configuration.</returns>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="properties" /> is <see langword="null" />.
/// </exception>
IDecoratorRegistrationBuilder<TService> WithMetadata(IEnumerable<KeyValuePair<string, object?>> properties);

/// <summary>
/// Provide the condition that determines whether the decorator is applied.
/// </summary>
/// <param name="condition">
/// A function that, when provided with an <see cref="IDecoratorContext"/> instance, determines
/// if the decorator should be applied.
/// </param>
/// <returns>The builder, to allow continued configuration.</returns>
/// <remarks>
/// This replaces any condition already supplied, including one passed to the
/// <c>RegisterDecorator</c> method that created this builder.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="condition" /> is <see langword="null" />.
/// </exception>
IDecoratorRegistrationBuilder<TService> WithCondition(Func<IDecoratorContext, bool> condition);
}
105 changes: 69 additions & 36 deletions src/Autofac/RegistrationExtensions.Decorators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,25 +128,24 @@ public static IRegistrationBuilder<TService, LightweightAdapterActivatorData, Dy
/// <typeparam name="TService">Service type being decorated.</typeparam>
/// <param name="builder">Container builder.</param>
/// <param name="condition">A function that when provided with an <see cref="IDecoratorContext"/> instance determines if the decorator should be applied.</param>
public static void RegisterDecorator<[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] TDecorator, TService>(this ContainerBuilder builder, Func<IDecoratorContext, bool>? condition = null)
/// <returns>The decorator registration for continued configuration.</returns>
public static IDecoratorRegistrationBuilder<TService> RegisterDecorator<[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] TDecorator, TService>(this ContainerBuilder builder, Func<IDecoratorContext, bool>? condition = null)
where TDecorator : notnull, TService
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}

var decoratorService = new DecoratorService(typeof(TService), condition);

var rb = RegistrationBuilder.ForType<TDecorator>().As(decoratorService);

var decoratorRegistration = rb.CreateRegistration();

var middleware = new DecoratorMiddleware(decoratorService, decoratorRegistration);
builder.RegisterServiceMiddleware<TService>(middleware, MiddlewareInsertionMode.StartOfPhase);
var rb = RegistrationBuilder.ForType<TDecorator>();

// Add the decorator to the registry so the pipeline gets built.
builder.RegisterCallback(crb => crb.Register(decoratorRegistration));
return AddDecorator<TService>(
builder,
typeof(TService),
rb.RegistrationData,
rb.ResolvePipeline,
decoratorService => rb.As(decoratorService).CreateRegistration(),
condition);
}

/// <summary>
Expand All @@ -158,7 +157,8 @@ public static IRegistrationBuilder<TService, LightweightAdapterActivatorData, Dy
/// of type <paramref name="serviceType"/>, which will be set to the instance being decorated.</param>
/// <param name="serviceType">Service type being decorated.</param>
/// <param name="condition">A function that when provided with an <see cref="IDecoratorContext"/> instance determines if the decorator should be applied.</param>
public static void RegisterDecorator(
/// <returns>The decorator registration for continued configuration.</returns>
public static IDecoratorRegistrationBuilder<object> RegisterDecorator(
this ContainerBuilder builder,
[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type decoratorType,
Type serviceType,
Expand All @@ -179,18 +179,15 @@ public static void RegisterDecorator(
throw new ArgumentNullException(nameof(serviceType));
}

var decoratorService = new DecoratorService(serviceType, condition);

var rb = RegistrationBuilder.ForType(decoratorType).As(decoratorService);
var rb = RegistrationBuilder.ForType(decoratorType);

var decoratorRegistration = rb.CreateRegistration();

var middleware = new DecoratorMiddleware(decoratorService, decoratorRegistration);

builder.RegisterServiceMiddleware(serviceType, middleware, MiddlewareInsertionMode.StartOfPhase);

// Add the decorator to the registry so the pipeline gets built.
builder.RegisterCallback(crb => crb.Register(decoratorRegistration));
return AddDecorator<object>(
builder,
serviceType,
rb.RegistrationData,
rb.ResolvePipeline,
decoratorService => rb.As(decoratorService).CreateRegistration(),
condition);
}

/// <summary>
Expand All @@ -202,7 +199,8 @@ public static void RegisterDecorator(
/// <param name="decorator">Function decorating a component instance that provides
/// <typeparamref name="TService"/>, given the context, parameters and service to decorate.</param>
/// <param name="condition">A function that when provided with an <see cref="IDecoratorContext"/> instance determines if the decorator should be applied.</param>
public static void RegisterDecorator<TService>(
/// <returns>The decorator registration for continued configuration.</returns>
public static IDecoratorRegistrationBuilder<TService> RegisterDecorator<TService>(
this ContainerBuilder builder,
Func<IComponentContext, IEnumerable<Parameter>, TService, TService> decorator,
Func<IDecoratorContext, bool>? condition = null)
Expand All @@ -218,25 +216,22 @@ public static void RegisterDecorator<TService>(
throw new ArgumentNullException(nameof(decorator));
}

var service = new DecoratorService(typeof(TService), condition);

var rb = RegistrationBuilder.ForDelegate((c, p) =>
{
var instance = (TService?)p
.OfType<TypedParameter>()
.FirstOrDefault(tp => tp.Type == typeof(TService))
?.Value ?? throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, RegistrationExtensionsResources.DecoratorRequiresInstanceParameter, typeof(TService).Name));
return decorator(c, p, instance);
}).As(service);

var decoratorRegistration = rb.CreateRegistration();

var middleware = new DecoratorMiddleware(service, decoratorRegistration);

builder.RegisterServiceMiddleware<TService>(middleware, MiddlewareInsertionMode.StartOfPhase);

// Add the decorator to the registry so the pipeline gets built.
builder.RegisterCallback(crb => crb.Register(decoratorRegistration));
});

return AddDecorator<TService>(
builder,
typeof(TService),
rb.RegistrationData,
rb.ResolvePipeline,
decoratorService => rb.As(decoratorService).CreateRegistration(),
condition);
}

/// <summary>
Expand Down Expand Up @@ -317,4 +312,42 @@ public static void RegisterGenericDecorator(

builder.RegisterServiceMiddlewareSource(new OpenGenericDecoratorMiddlewareSource(decoratorService, genericRegistration.RegistrationData, genericRegistration.ActivatorData));
}

/// <summary>
/// Creates the builder used to configure a decorator registration, and schedules the
/// registration itself for when the container is built.
/// </summary>
/// <typeparam name="TService">Service type being decorated.</typeparam>
/// <param name="builder">Container builder.</param>
/// <param name="serviceType">The service type being decorated.</param>
/// <param name="registrationData">Registration data for the decorator registration.</param>
/// <param name="pipelineBuilder">Resolve pipeline builder for the decorator registration.</param>
/// <param name="registrationFactory">Factory producing the decorator's component registration.</param>
/// <param name="condition">A function that determines if the decorator should be applied.</param>
/// <returns>The decorator registration for continued configuration.</returns>
/// <remarks>
/// The decorator's component registration is deliberately not created here. Deferring it to
/// container build time is what allows the returned builder to keep configuring the
/// registration - in particular its resolve pipeline, which
/// <see cref="RegistrationBuilder"/>.<c>CreateRegistration</c> clones.
/// </remarks>
private static DecoratorRegistrationBuilder<TService> AddDecorator<TService>(
ContainerBuilder builder,
Type serviceType,
RegistrationData registrationData,
IResolvePipelineBuilder pipelineBuilder,
Func<DecoratorService, IComponentRegistration> registrationFactory,
Func<IDecoratorContext, bool>? condition)
{
var decoratorBuilder = new DecoratorRegistrationBuilder<TService>(
serviceType,
registrationData,
pipelineBuilder,
registrationFactory,
condition);

builder.RegisterCallback(decoratorBuilder.Register);

return decoratorBuilder;
}
}
Loading
Loading