Skip to content

Commit cc85c5a

Browse files
committed
[Mvc] Support IAsyncDisposable in controllers, pages and view components
* Adds async versions of Release to the relevant interfaces in different factories and activators. * The new methods offer a default interface implementation that will delegate to the syncrhonous version and just call dispose. * Our implementations will override the default implementation and handle disposing IAsyncDisposable implementations. * Our implementations will favor IAsyncDisposable over IDisposable when both interfaces are implemented. * Extenders will have to override the new methods included to support IAsyncDisposable instances.
1 parent 1d2bb10 commit cc85c5a

File tree

52 files changed

+1469
-145
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1469
-145
lines changed

src/Mvc/Mvc.Core/src/Controllers/ControllerActivatorProvider.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Reflection;
6+
using System.Threading.Tasks;
67
using Microsoft.AspNetCore.Mvc.Core;
78
using Microsoft.Extensions.DependencyInjection;
89

@@ -14,8 +15,11 @@ namespace Microsoft.AspNetCore.Mvc.Controllers
1415
public class ControllerActivatorProvider : IControllerActivatorProvider
1516
{
1617
private static readonly Action<ControllerContext, object> _dispose = Dispose;
18+
private static readonly Func<ControllerContext, object, ValueTask> _disposeAsync = DisposeAsync;
19+
private static readonly Func<ControllerContext, object, ValueTask> _syncDisposeAsync = SyncDisposeAsync;
1720
private readonly Func<ControllerContext, object> _controllerActivatorCreate;
1821
private readonly Action<ControllerContext, object> _controllerActivatorRelease;
22+
private readonly Func<ControllerContext, object, ValueTask> _controllerActivatorReleaseAsync;
1923

2024
/// <summary>
2125
/// Initializes a new instance of <see cref="ControllerActivatorProvider"/>.
@@ -33,6 +37,7 @@ public ControllerActivatorProvider(IControllerActivator controllerActivator)
3337
{
3438
_controllerActivatorCreate = controllerActivator.Create;
3539
_controllerActivatorRelease = controllerActivator.Release;
40+
_controllerActivatorReleaseAsync = controllerActivator.ReleaseAsync;
3641
}
3742
}
3843

@@ -83,6 +88,32 @@ public Action<ControllerContext, object> CreateReleaser(ControllerActionDescript
8388
return null;
8489
}
8590

91+
/// <inheritdoc/>
92+
public Func<ControllerContext, object, ValueTask> CreateAsyncReleaser(ControllerActionDescriptor descriptor)
93+
{
94+
if (descriptor == null)
95+
{
96+
throw new ArgumentNullException(nameof(descriptor));
97+
}
98+
99+
if (_controllerActivatorReleaseAsync != null)
100+
{
101+
return _controllerActivatorReleaseAsync;
102+
}
103+
104+
if (typeof(IAsyncDisposable).GetTypeInfo().IsAssignableFrom(descriptor.ControllerTypeInfo))
105+
{
106+
return _disposeAsync;
107+
}
108+
109+
if (typeof(IDisposable).GetTypeInfo().IsAssignableFrom(descriptor.ControllerTypeInfo))
110+
{
111+
return _syncDisposeAsync;
112+
}
113+
114+
return null;
115+
}
116+
86117
private static void Dispose(ControllerContext context, object controller)
87118
{
88119
if (controller == null)
@@ -92,5 +123,21 @@ private static void Dispose(ControllerContext context, object controller)
92123

93124
((IDisposable)controller).Dispose();
94125
}
126+
127+
private static ValueTask DisposeAsync(ControllerContext context, object controller)
128+
{
129+
if (controller == null)
130+
{
131+
throw new ArgumentNullException(nameof(controller));
132+
}
133+
134+
return ((IAsyncDisposable)controller).DisposeAsync();
135+
}
136+
137+
private static ValueTask SyncDisposeAsync(ControllerContext context, object controller)
138+
{
139+
Dispose(context, controller);
140+
return default;
141+
}
95142
}
96143
}

src/Mvc/Mvc.Core/src/Controllers/ControllerFactoryProvider.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
7+
using System.Threading.Tasks;
78
using Microsoft.AspNetCore.Mvc.Core;
89

910
namespace Microsoft.AspNetCore.Mvc.Controllers
@@ -13,6 +14,7 @@ internal class ControllerFactoryProvider : IControllerFactoryProvider
1314
private readonly IControllerActivatorProvider _activatorProvider;
1415
private readonly Func<ControllerContext, object> _factoryCreateController;
1516
private readonly Action<ControllerContext, object> _factoryReleaseController;
17+
private readonly Func<ControllerContext, object, ValueTask> _factoryReleaseControllerAsync;
1618
private readonly IControllerPropertyActivator[] _propertyActivators;
1719

1820
public ControllerFactoryProvider(
@@ -37,6 +39,7 @@ public ControllerFactoryProvider(
3739
{
3840
_factoryCreateController = controllerFactory.CreateController;
3941
_factoryReleaseController = controllerFactory.ReleaseController;
42+
_factoryReleaseControllerAsync = controllerFactory.ReleaseControllerAsync;
4043
}
4144

4245
_propertyActivators = propertyActivators.ToArray();
@@ -104,6 +107,30 @@ public Action<ControllerContext, object> CreateControllerReleaser(ControllerActi
104107
return _activatorProvider.CreateReleaser(descriptor);
105108
}
106109

110+
public Func<ControllerContext, object, ValueTask> CreateAsyncControllerReleaser(ControllerActionDescriptor descriptor)
111+
{
112+
if (descriptor == null)
113+
{
114+
throw new ArgumentNullException(nameof(descriptor));
115+
}
116+
117+
var controllerType = descriptor.ControllerTypeInfo?.AsType();
118+
if (controllerType == null)
119+
{
120+
throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull(
121+
nameof(descriptor.ControllerTypeInfo),
122+
nameof(descriptor)),
123+
nameof(descriptor));
124+
}
125+
126+
if (_factoryReleaseControllerAsync != null)
127+
{
128+
return _factoryReleaseControllerAsync;
129+
}
130+
131+
return _activatorProvider.CreateAsyncReleaser(descriptor);
132+
}
133+
107134
private Action<ControllerContext, object>[] GetPropertiesToActivate(ControllerActionDescriptor actionDescriptor)
108135
{
109136
var propertyActivators = new Action<ControllerContext, object>[_propertyActivators.Length];

src/Mvc/Mvc.Core/src/Controllers/DefaultControllerActivator.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Threading.Tasks;
56
using Microsoft.AspNetCore.Mvc.Core;
67
using Microsoft.AspNetCore.Mvc.Infrastructure;
78

@@ -74,5 +75,26 @@ public void Release(ControllerContext context, object controller)
7475
disposable.Dispose();
7576
}
7677
}
78+
79+
public ValueTask ReleaseAsync(ControllerContext context, object controller)
80+
{
81+
if (context == null)
82+
{
83+
throw new ArgumentNullException(nameof(context));
84+
}
85+
86+
if (controller == null)
87+
{
88+
throw new ArgumentNullException(nameof(controller));
89+
}
90+
91+
if (controller is IAsyncDisposable asyncDisposable)
92+
{
93+
return asyncDisposable.DisposeAsync();
94+
}
95+
96+
Release(context, controller);
97+
return default;
98+
}
7799
}
78100
}

src/Mvc/Mvc.Core/src/Controllers/DefaultControllerFactory.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
7+
using System.Threading.Tasks;
78
using Microsoft.AspNetCore.Mvc.Core;
89

910
namespace Microsoft.AspNetCore.Mvc.Controllers
@@ -83,5 +84,20 @@ public void ReleaseController(ControllerContext context, object controller)
8384

8485
_controllerActivator.Release(context, controller);
8586
}
87+
88+
public ValueTask ReleaseControllerAsync(ControllerContext context, object controller)
89+
{
90+
if (context == null)
91+
{
92+
throw new ArgumentNullException(nameof(context));
93+
}
94+
95+
if (controller == null)
96+
{
97+
throw new ArgumentNullException(nameof(controller));
98+
}
99+
100+
return _controllerActivator.ReleaseAsync(context, controller);
101+
}
86102
}
87103
}

src/Mvc/Mvc.Core/src/Controllers/IControllerActivator.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System.Threading.Tasks;
5+
46
namespace Microsoft.AspNetCore.Mvc.Controllers
57
{
68
/// <summary>
@@ -20,5 +22,16 @@ public interface IControllerActivator
2022
/// <param name="context">The <see cref="ControllerContext"/> for the executing action.</param>
2123
/// <param name="controller">The controller to release.</param>
2224
void Release(ControllerContext context, object controller);
25+
26+
/// <summary>
27+
/// Releases a controller asynchronously.
28+
/// </summary>
29+
/// <param name="context">The <see cref="ControllerContext"/> for the executing action.</param>
30+
/// <param name="controller">The controller to release.</param>
31+
ValueTask ReleaseAsync(ControllerContext context, object controller)
32+
{
33+
Release(context, controller);
34+
return default;
35+
}
2336
}
24-
}
37+
}

src/Mvc/Mvc.Core/src/Controllers/IControllerActivatorProvider.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Threading.Tasks;
56

67
namespace Microsoft.AspNetCore.Mvc.Controllers
78
{
@@ -23,5 +24,20 @@ public interface IControllerActivatorProvider
2324
/// <param name="descriptor">The <see cref="ControllerActionDescriptor"/>.</param>
2425
/// <returns>The delegate used to dispose the activated controller.</returns>
2526
Action<ControllerContext, object> CreateReleaser(ControllerActionDescriptor descriptor);
27+
28+
/// <summary>
29+
/// Creates an <see cref="Action"/> that releases a controller.
30+
/// </summary>
31+
/// <param name="descriptor">The <see cref="ControllerActionDescriptor"/>.</param>
32+
/// <returns>The delegate used to dispose the activated controller.</returns>
33+
Func<ControllerContext, object, ValueTask> CreateAsyncReleaser(ControllerActionDescriptor descriptor)
34+
{
35+
var releaser = CreateReleaser(descriptor);
36+
return (context, controller) =>
37+
{
38+
releaser(context, controller);
39+
return default;
40+
};
41+
}
2642
}
2743
}

src/Mvc/Mvc.Core/src/Controllers/IControllerFactory.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System.Threading.Tasks;
5+
46
namespace Microsoft.AspNetCore.Mvc.Controllers
57
{
68
/// <summary>
@@ -21,5 +23,16 @@ public interface IControllerFactory
2123
/// <param name="context"><see cref="ControllerContext"/> for the executing action.</param>
2224
/// <param name="controller">The controller.</param>
2325
void ReleaseController(ControllerContext context, object controller);
26+
27+
/// <summary>
28+
/// Releases a controller instance asynchronously.
29+
/// </summary>
30+
/// <param name="context"><see cref="ControllerContext"/> for the executing action.</param>
31+
/// <param name="controller">The controller.</param>
32+
ValueTask ReleaseControllerAsync(ControllerContext context, object controller)
33+
{
34+
ReleaseController(context, controller);
35+
return default;
36+
}
2437
}
2538
}

src/Mvc/Mvc.Core/src/Controllers/IControllerFactoryProvider.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Threading.Tasks;
56

67
namespace Microsoft.AspNetCore.Mvc.Controllers
78
{
@@ -23,5 +24,20 @@ public interface IControllerFactoryProvider
2324
/// <param name="descriptor">The <see cref="ControllerActionDescriptor"/>.</param>
2425
/// <returns>The delegate used to release the created controller.</returns>
2526
Action<ControllerContext, object> CreateControllerReleaser(ControllerActionDescriptor descriptor);
27+
28+
/// <summary>
29+
/// Releases a controller asynchronously.
30+
/// </summary>
31+
/// <param name="descriptor">The <see cref="ControllerActionDescriptor"/>.</param>
32+
/// <returns>The delegate used to release the created controller asynchronously.</returns>
33+
Func<ControllerContext, object, ValueTask> CreateAsyncControllerReleaser(ControllerActionDescriptor descriptor)
34+
{
35+
var releaser = CreateControllerReleaser(descriptor);
36+
return (context, controller) =>
37+
{
38+
releaser(context, controller);
39+
return default;
40+
};
41+
}
2642
}
2743
}

src/Mvc/Mvc.Core/src/Infrastructure/ControllerActionInvoker.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,14 @@ internal ControllerActionInvoker(
4848
// Internal for testing
4949
internal ControllerContext ControllerContext => _controllerContext;
5050

51-
protected override void ReleaseResources()
51+
protected override ValueTask ReleaseResources()
5252
{
5353
if (_instance != null && _cacheEntry.ControllerReleaser != null)
5454
{
55-
_cacheEntry.ControllerReleaser(_controllerContext, _instance);
55+
return _cacheEntry.ControllerReleaser(_controllerContext, _instance);
5656
}
57+
58+
return default;
5759
}
5860

5961
private Task Next(ref State next, ref Scope scope, ref object? state, ref bool isCompleted)

src/Mvc/Mvc.Core/src/Infrastructure/ControllerActionInvokerCache.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public ControllerActionInvokerCache(
6161
parameterDefaultValues);
6262

6363
var controllerFactory = _controllerFactoryProvider.CreateControllerFactory(actionDescriptor);
64-
var controllerReleaser = _controllerFactoryProvider.CreateControllerReleaser(actionDescriptor);
64+
var controllerReleaser = _controllerFactoryProvider.CreateAsyncControllerReleaser(actionDescriptor);
6565
var propertyBinderFactory = ControllerBinderDelegateProvider.CreateBinderDelegate(
6666
_parameterBinder,
6767
_modelBinderFactory,

src/Mvc/Mvc.Core/src/Infrastructure/ControllerActionInvokerCacheEntry.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#nullable enable
55

66
using System;
7+
using System.Threading.Tasks;
78
using Microsoft.AspNetCore.Mvc.Controllers;
89
using Microsoft.AspNetCore.Mvc.Filters;
910
using Microsoft.Extensions.Internal;
@@ -15,7 +16,7 @@ internal class ControllerActionInvokerCacheEntry
1516
internal ControllerActionInvokerCacheEntry(
1617
FilterItem[] cachedFilters,
1718
Func<ControllerContext, object> controllerFactory,
18-
Action<ControllerContext, object> controllerReleaser,
19+
Func<ControllerContext, object, ValueTask> controllerReleaser,
1920
ControllerBinderDelegate controllerBinderDelegate,
2021
ObjectMethodExecutor objectMethodExecutor,
2122
ActionMethodExecutor actionMethodExecutor)
@@ -32,7 +33,7 @@ internal ControllerActionInvokerCacheEntry(
3233

3334
public Func<ControllerContext, object> ControllerFactory { get; }
3435

35-
public Action<ControllerContext, object> ControllerReleaser { get; }
36+
public Func<ControllerContext, object, ValueTask> ControllerReleaser { get; }
3637

3738
public ControllerBinderDelegate ControllerBinderDelegate { get; }
3839

0 commit comments

Comments
 (0)