Skip to content

Commit

Permalink
Add support for Func<TArgs, Task> handlers for Core Autofac Events
Browse files Browse the repository at this point in the history
  • Loading branch information
tillig committed Jul 22, 2020
2 parents 61f64b0 + c9f5cf8 commit d13dfd6
Show file tree
Hide file tree
Showing 9 changed files with 300 additions and 8 deletions.
4 changes: 2 additions & 2 deletions src/Autofac/Autofac.csproj
Expand Up @@ -44,10 +44,10 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0">
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.0-beta1.final">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.0.0">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0-beta2.final">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" Condition="Exists('$(MSBuildThisFileDirectory)../../.git')">
Expand Down
23 changes: 23 additions & 0 deletions src/Autofac/Builder/IRegistrationBuilder.cs
Expand Up @@ -26,6 +26,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using Autofac.Core;
using Autofac.Core.Resolving.Pipeline;

Expand Down Expand Up @@ -257,20 +258,42 @@ public interface IRegistrationBuilder<out TLimit, out TActivatorData, out TRegis
/// <returns>A registration builder allowing further configuration of the component.</returns>
IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> OnPreparing(Action<PreparingEventArgs> handler);

/// <summary>
/// Add an async handler for the Preparing event. This event allows manipulating of the parameters
/// that will be provided to the component.
/// </summary>
/// <param name="handler">An event handler; the resolve process will not continue until the returned task completes.</param>
/// <returns>A registration builder allowing further configuration of the component.</returns>
IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> OnPreparing(Func<PreparingEventArgs, ValueTask> handler);

/// <summary>
/// Add a handler for the Activating event.
/// </summary>
/// <param name="handler">The event handler.</param>
/// <returns>A registration builder allowing further configuration of the component.</returns>
IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> OnActivating(Action<IActivatingEventArgs<TLimit>> handler);

/// <summary>
/// Add an async handler for the Activating event.
/// </summary>
/// <param name="handler">An event handler; the resolve process will not continue until the returned task completes.</param>
/// <returns>A registration builder allowing further configuration of the component.</returns>
IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> OnActivating(Func<IActivatingEventArgs<TLimit>, ValueTask> handler);

/// <summary>
/// Add a handler for the Activated event.
/// </summary>
/// <param name="handler">The event handler.</param>
/// <returns>A registration builder allowing further configuration of the component.</returns>
IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> OnActivated(Action<IActivatedEventArgs<TLimit>> handler);

/// <summary>
/// Add a handler for the Activated event.
/// </summary>
/// <param name="handler">An event handler; the resolve process will not continue until the returned task completes.</param>
/// <returns>A registration builder allowing further configuration of the component.</returns>
IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> OnActivated(Func<IActivatedEventArgs<TLimit>, ValueTask> handler);

/// <summary>
/// Configure the component so that any properties whose types are registered in the
/// container and follow specific criteria will be wired to instances of the appropriate service.
Expand Down
Expand Up @@ -27,6 +27,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Autofac.Core;
using Autofac.Core.Activators.Reflection;
using Autofac.Core.Lifetime;
Expand Down Expand Up @@ -418,6 +419,25 @@ public RegistrationBuilder(Service defaultService, TActivatorData activatorData,
return this;
}

/// <inheritdoc/>
public IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> OnPreparing(Func<PreparingEventArgs, ValueTask> handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}

return OnPreparing(args =>
{
var vt = handler(args);
if (!vt.IsCompletedSuccessfully)
{
vt.ConfigureAwait(false).GetAwaiter().GetResult();
}
});
}

/// <summary>
/// Add a handler for the Activating event.
/// </summary>
Expand All @@ -444,6 +464,25 @@ public RegistrationBuilder(Service defaultService, TActivatorData activatorData,
return this;
}

/// <inheritdoc/>
public IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> OnActivating(Func<IActivatingEventArgs<TLimit>, ValueTask> handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}

return OnActivating(args =>
{
var vt = handler(args);
if (!vt.IsCompletedSuccessfully)
{
vt.ConfigureAwait(false).GetAwaiter().GetResult();
}
});
}

/// <summary>
/// Add a handler for the Activated event.
/// </summary>
Expand Down Expand Up @@ -484,6 +523,25 @@ public RegistrationBuilder(Service defaultService, TActivatorData activatorData,
return this;
}

/// <inheritdoc/>
public IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> OnActivated(Func<IActivatedEventArgs<TLimit>, ValueTask> handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}

return OnActivated(args =>
{
var vt = handler(args);
if (!vt.IsCompletedSuccessfully)
{
vt.ConfigureAwait(false).GetAwaiter().GetResult();
}
});
}

/// <summary>
/// Configure the component so that any properties whose types are registered in the
/// container and follow specific criteria will be wired to instances of the appropriate service.
Expand Down
2 changes: 1 addition & 1 deletion src/Autofac/Core/Registration/ServiceRegistrationInfo.cs
Expand Up @@ -45,7 +45,7 @@ internal class ServiceRegistrationInfo : IResolvePipelineBuilder
[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "The _service field is useful in debugging and diagnostics.")]
private readonly Service _service;

private IComponentRegistration? _fixedRegistration = null;
private IComponentRegistration? _fixedRegistration;

/// <summary>
/// List of implicit default service implementations. Overriding default implementations are appended to the end,
Expand Down
4 changes: 2 additions & 2 deletions src/Autofac/Core/Resolving/ResolveEventType.cs
Expand Up @@ -8,7 +8,7 @@ namespace Autofac.Core.Resolving.Middleware
public enum ResolveEventType
{
/// <summary>
/// Event type for the OnPreparing event registered by <see cref="IRegistrationBuilder{TLimit, TActivatorData, TRegistrationStyle}.OnPreparing"/>.
/// Event type for the OnPreparing event registered by <see cref="IRegistrationBuilder{TLimit, TActivatorData, TRegistrationStyle}.OnPreparing(System.Action{PreparingEventArgs})"/>.
/// </summary>
OnPreparing,

Expand All @@ -23,7 +23,7 @@ public enum ResolveEventType
OnActivated,

/// <summary>
/// Event type for the OnRelease event registered by <see cref="RegistrationExtensions.OnRelease"/>
/// Event type for the OnRelease event registered by <see cref="RegistrationExtensions.OnRelease{TLimit, TActivatorData, TRegistrationStyle}(IRegistrationBuilder{TLimit, TActivatorData, TRegistrationStyle}, System.Action{TLimit})"/>
/// </summary>
OnRelease
}
Expand Down
9 changes: 7 additions & 2 deletions src/Autofac/Core/Resolving/ResolveOperationBase.cs
Expand Up @@ -41,8 +41,8 @@ public abstract class ResolveOperationBase : IResolveOperation
private const int SuccessListInitialCapacity = 32;

private bool _ended;
private List<ResolveRequestContext> _successfulRequests = new List<ResolveRequestContext>(SuccessListInitialCapacity);
private int _nextCompleteSuccessfulRequestStartPos = 0;
private readonly List<ResolveRequestContext> _successfulRequests = new List<ResolveRequestContext>(SuccessListInitialCapacity);
private int _nextCompleteSuccessfulRequestStartPos;

/// <summary>
/// Initializes a new instance of the <see cref="ResolveOperationBase"/> class.
Expand Down Expand Up @@ -179,6 +179,11 @@ protected object ExecuteOperation(ResolveRequest request)
/// <inheritdoc />
public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request)
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}

if (_ended) throw new ObjectDisposedException(ResolveOperationResources.TemporaryContextDisposed, innerException: null);

// Create a new request context.
Expand Down
40 changes: 39 additions & 1 deletion src/Autofac/RegistrationExtensions.EventHandler.cs
Expand Up @@ -25,6 +25,7 @@

using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Autofac.Builder;
using Autofac.Core;
using Autofac.Core.Resolving.Middleware;
Expand Down Expand Up @@ -94,7 +95,6 @@ public static partial class RegistrationExtensions
/// <param name="registration">Registration to set release action for.</param>
/// <param name="releaseAction">An action to perform instead of disposing the instance.</param>
/// <returns>Registration builder allowing the registration to be configured.</returns>
/// <remarks>Only one release action can be configured per registration.</remarks>
public static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle>
OnRelease<TLimit, TActivatorData, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> registration,
Expand Down Expand Up @@ -124,5 +124,43 @@ public static partial class RegistrationExtensions

return registration;
}

/// <summary>
/// Run a supplied async action instead of disposing instances when they're no
/// longer required.
/// </summary>
/// <typeparam name="TLimit">Registration limit type.</typeparam>
/// <typeparam name="TActivatorData">Activator data type.</typeparam>
/// <typeparam name="TRegistrationStyle">Registration style.</typeparam>
/// <param name="registration">Registration to set release action for.</param>
/// <param name="releaseAction">
/// An action to perform instead of disposing the instance.
/// The release/disposal process will not continue until the returned task completes.
/// </param>
/// <returns>Registration builder allowing the registration to be configured.</returns>
public static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle>
OnRelease<TLimit, TActivatorData, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> registration,
Func<TLimit, ValueTask> releaseAction)
{
if (registration == null) throw new ArgumentNullException(nameof(registration));
if (releaseAction == null) throw new ArgumentNullException(nameof(releaseAction));

registration.ExternallyOwned();

var middleware = new CoreEventMiddleware(ResolveEventType.OnRelease, PipelinePhase.Activation, (ctxt, next) =>
{
// Continue down the pipeline.
next(ctxt);
// Use an async release action that invokes the release callback in a proper async/await flow if someone
// is using actual async disposal.
ctxt.ActivationScope.Disposer.AddInstanceForAsyncDisposal(new AsyncReleaseAction<TLimit>(releaseAction, () => (TLimit)ctxt.Instance!));
});

registration.ResolvePipeline.Use(middleware, MiddlewareInsertionMode.StartOfPhase);

return registration;
}
}
}
69 changes: 69 additions & 0 deletions src/Autofac/Util/AsyncReleaseAction.cs
@@ -0,0 +1,69 @@
// Copyright (c) Autofac Project. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;
using System.Threading.Tasks;

namespace Autofac.Util
{
/// <summary>
/// Adapts an async action to the <see cref="IAsyncDisposable"/> interface.
/// </summary>
internal class AsyncReleaseAction<TLimit> : Disposable
{
private readonly Func<TLimit, ValueTask> _action;
private readonly Func<TLimit> _factory;

/// <summary>
/// Initializes a new instance of the <see cref="AsyncReleaseAction{TLimit}"/> class.
/// </summary>
/// <param name="action">
/// The async action to execute on disposal.
/// </param>
/// <param name="factory">
/// A factory that retrieves the value on which the <paramref name="action" />
/// should be executed.
/// </param>
public AsyncReleaseAction(Func<TLimit, ValueTask> action, Func<TLimit> factory)
{
if (action == null) throw new ArgumentNullException(nameof(action));
if (factory == null) throw new ArgumentNullException(nameof(factory));

_action = action;
_factory = factory;
}

/// <inheritdoc/>
protected override async ValueTask DisposeAsync(bool disposing)
{
// Value retrieval for the disposal is deferred until
// disposal runs to ensure any calls to, say, .ReplaceInstance()
// during .OnActivating() will be accounted for.
if (disposing)
{
await _action(_factory()).ConfigureAwait(false);
}

base.Dispose(disposing);
}

/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
// Value retrieval for the disposal is deferred until
// disposal runs to ensure any calls to, say, .ReplaceInstance()
// during .OnActivating() will be accounted for.
if (disposing)
{
var vt = _action(_factory());

if (!vt.IsCompletedSuccessfully)
{
vt.ConfigureAwait(false).GetAwaiter().GetResult();
}
}

base.Dispose(disposing);
}
}
}

0 comments on commit d13dfd6

Please sign in to comment.