Skip to content

Commit 32c4a6c

Browse files
committed
Add suppor for IAsyncDisposable on view components
1 parent 762b70e commit 32c4a6c

6 files changed

+93
-26
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
#nullable enable
2+
~Microsoft.AspNetCore.Mvc.ViewComponents.IViewComponentActivator.ReleaseAsync(Microsoft.AspNetCore.Mvc.ViewComponents.ViewComponentContext context, object viewComponent) -> System.Threading.Tasks.ValueTask
3+
~Microsoft.AspNetCore.Mvc.ViewComponents.IViewComponentFactory.ReleaseViewComponentAsync(Microsoft.AspNetCore.Mvc.ViewComponents.ViewComponentContext context, object component) -> System.Threading.Tasks.ValueTask

src/Mvc/Mvc.ViewFeatures/src/ViewComponents/DefaultViewComponentActivator.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.Infrastructure;
67
using Microsoft.AspNetCore.Mvc.ViewFeatures;
78

@@ -77,5 +78,26 @@ public void Release(ViewComponentContext context, object viewComponent)
7778
disposable.Dispose();
7879
}
7980
}
81+
82+
public ValueTask ReleaseAsync(ViewComponentContext context, object viewComponent)
83+
{
84+
if (context == null)
85+
{
86+
throw new InvalidOperationException(nameof(context));
87+
}
88+
89+
if (viewComponent == null)
90+
{
91+
throw new InvalidOperationException(nameof(viewComponent));
92+
}
93+
94+
if (viewComponent is IAsyncDisposable disposable)
95+
{
96+
return disposable.DisposeAsync();
97+
}
98+
99+
Release(context, viewComponent);
100+
return default;
101+
}
80102
}
81103
}

src/Mvc/Mvc.ViewFeatures/src/ViewComponents/DefaultViewComponentFactory.cs

Lines changed: 17 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.Concurrent;
66
using System.Reflection;
7+
using System.Threading.Tasks;
78
using Microsoft.Extensions.Internal;
89

910
namespace Microsoft.AspNetCore.Mvc.ViewComponents
@@ -88,5 +89,20 @@ public void ReleaseViewComponent(ViewComponentContext context, object component)
8889

8990
_activator.Release(context, component);
9091
}
92+
93+
ValueTask ReleaseViewComponentAsync(ViewComponentContext context, object component)
94+
{
95+
if (context == null)
96+
{
97+
throw new ArgumentNullException(nameof(context));
98+
}
99+
100+
if (component == null)
101+
{
102+
throw new ArgumentNullException(nameof(component));
103+
}
104+
105+
return _activator.ReleaseAsync(context, component);
106+
}
91107
}
92108
}

src/Mvc/Mvc.ViewFeatures/src/ViewComponents/DefaultViewComponentInvoker.cs

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -81,24 +81,34 @@ public async Task InvokeAsync(ViewComponentContext context)
8181
}
8282

8383
IViewComponentResult result;
84-
if (executor.IsMethodAsync)
84+
object? component = null;
85+
try
8586
{
86-
result = await InvokeAsyncCore(executor, context);
87+
component = _viewComponentFactory.CreateViewComponent(context);
88+
if (executor.IsMethodAsync)
89+
{
90+
result = await InvokeAsyncCore(executor, component, context);
91+
}
92+
else
93+
{
94+
// We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still
95+
// execute the IViewResult asynchronously.
96+
result = InvokeSyncCore(executor, component, context);
97+
}
8798
}
88-
else
99+
finally
89100
{
90-
// We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still
91-
// execute the IViewResult asynchronously.
92-
result = InvokeSyncCore(executor, context);
101+
if (component != null)
102+
{
103+
await _viewComponentFactory.ReleaseViewComponentAsync(context, executor);
104+
}
93105
}
94106

95107
await result.ExecuteAsync(context);
96108
}
97109

98-
private async Task<IViewComponentResult> InvokeAsyncCore(ObjectMethodExecutor executor, ViewComponentContext context)
110+
private async Task<IViewComponentResult> InvokeAsyncCore(ObjectMethodExecutor executor, object component, ViewComponentContext context)
99111
{
100-
var component = _viewComponentFactory.CreateViewComponent(context);
101-
102112
using (_logger.ViewComponentScope(context))
103113
{
104114
var arguments = PrepareArguments(context.Arguments, executor);
@@ -150,16 +160,12 @@ private async Task<IViewComponentResult> InvokeAsyncCore(ObjectMethodExecutor ex
150160
_logger.ViewComponentExecuted(context, stopwatch.GetElapsedTime(), viewComponentResult);
151161
_diagnosticListener.AfterViewComponent(context, viewComponentResult, component);
152162

153-
_viewComponentFactory.ReleaseViewComponent(context, component);
154-
155163
return viewComponentResult;
156164
}
157165
}
158166

159-
private IViewComponentResult InvokeSyncCore(ObjectMethodExecutor executor, ViewComponentContext context)
167+
private IViewComponentResult InvokeSyncCore(ObjectMethodExecutor executor, object component, ViewComponentContext context)
160168
{
161-
var component = _viewComponentFactory.CreateViewComponent(context);
162-
163169
using (_logger.ViewComponentScope(context))
164170
{
165171
var arguments = PrepareArguments(context.Arguments, executor);
@@ -170,21 +176,12 @@ private IViewComponentResult InvokeSyncCore(ObjectMethodExecutor executor, ViewC
170176
var stopwatch = ValueStopwatch.StartNew();
171177
object? result;
172178

173-
try
174-
{
175179
result = executor.Execute(component, arguments);
176-
}
177-
finally
178-
{
179-
_viewComponentFactory.ReleaseViewComponent(context, component);
180-
}
181180

182181
var viewComponentResult = CoerceToViewComponentResult(result);
183182
_logger.ViewComponentExecuted(context, stopwatch.GetElapsedTime(), viewComponentResult);
184183
_diagnosticListener.AfterViewComponent(context, viewComponentResult, component);
185184

186-
_viewComponentFactory.ReleaseViewComponent(context, component);
187-
188185
return viewComponentResult;
189186
}
190187
}

src/Mvc/Mvc.ViewFeatures/src/ViewComponents/IViewComponentActivator.cs

Lines changed: 17 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.ViewComponents
57
{
68
/// <summary>
@@ -24,5 +26,19 @@ public interface IViewComponentActivator
2426
/// </param>
2527
/// <param name="viewComponent">The <see cref="ViewComponent"/> to release.</param>
2628
void Release(ViewComponentContext context, object viewComponent);
29+
30+
/// <summary>
31+
/// Releases a ViewComponent instance.
32+
/// </summary>
33+
/// <param name="context">
34+
/// The <see cref="ViewComponentContext"/> associated with the <paramref name="viewComponent"/>.
35+
/// </param>
36+
/// <param name="viewComponent">The <see cref="ViewComponent"/> to release.</param>
37+
/// <returns>A <see cref="ValueTask"/> that completes when the view component has been disposed.</returns>
38+
ValueTask ReleaseAsync(ViewComponentContext context, object viewComponent)
39+
{
40+
Release(context, viewComponent);
41+
return default;
42+
}
2743
}
28-
}
44+
}

src/Mvc/Mvc.ViewFeatures/src/ViewComponents/IViewComponentFactory.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +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

4+
using System.Threading.Tasks;
5+
46
namespace Microsoft.AspNetCore.Mvc.ViewComponents
57
{
68
/// <summary>
@@ -21,5 +23,17 @@ public interface IViewComponentFactory
2123
/// <param name="context">The context associated with the <paramref name="component"/>.</param>
2224
/// <param name="component">The view component.</param>
2325
void ReleaseViewComponent(ViewComponentContext context, object component);
26+
27+
/// <summary>
28+
/// Releases a view component instance asynchronously.
29+
/// </summary>
30+
/// <param name="context">The context associated with the <paramref name="component"/>.</param>
31+
/// <param name="component">The view component.</param>
32+
/// <returns>A <see cref="ValueTask"/> that completes when the view component has been released.</returns>
33+
ValueTask ReleaseViewComponentAsync(ViewComponentContext context, object component)
34+
{
35+
ReleaseViewComponent(context, component);
36+
return default;
37+
}
2438
}
2539
}

0 commit comments

Comments
 (0)