Skip to content
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

Add support for Func<TArgs, Task> handlers for Core Autofac Events #1172

Merged
merged 4 commits into from Jul 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
}
}
}