Skip to content

Commit baffacb

Browse files
committed
Enable HttpContext.DisposeOnPipelineCompleted
This is similar to HttpResponse.RegisterForDispose, but allows someone to unsubscribe and track if it is still active.
1 parent 8f8ca9c commit baffacb

File tree

5 files changed

+137
-0
lines changed

5 files changed

+137
-0
lines changed

src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections;
5+
using System.Collections.Generic;
56
using System.Diagnostics.CodeAnalysis;
67
using System.Security.Claims;
78
using System.Security.Principal;
@@ -72,9 +73,33 @@ public IPrincipal User
7273
return null;
7374
}
7475

76+
public ISubscriptionToken DisposeOnPipelineCompleted(IDisposable target)
77+
{
78+
var token = new DisposeOnPipelineSubscriptionToken(target);
79+
_context.Response.RegisterForDispose(token);
80+
return token;
81+
}
82+
7583
[return: NotNullIfNotNull("context")]
7684
public static implicit operator HttpContext?(HttpContextCore? context) => context?.GetAdapter();
7785

7886
[return: NotNullIfNotNull("context")]
7987
public static implicit operator HttpContextCore?(HttpContext? context) => context?._context;
88+
89+
private sealed class DisposeOnPipelineSubscriptionToken : ISubscriptionToken, IDisposable
90+
{
91+
private IDisposable? _other;
92+
93+
public DisposeOnPipelineSubscriptionToken(IDisposable other) => _other = other;
94+
95+
bool ISubscriptionToken.IsActive => _other is not null;
96+
97+
void ISubscriptionToken.Unsubscribe() => _other = null;
98+
99+
void IDisposable.Dispose()
100+
{
101+
_other?.Dispose();
102+
_other = null;
103+
}
104+
}
80105
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Web;
5+
6+
public interface ISubscriptionToken
7+
{
8+
/// <summary>
9+
/// Returns a value stating whether the subscription is currently active
10+
/// </summary>
11+
bool IsActive { get; }
12+
13+
/// <summary>
14+
/// Unsubscribes from the event
15+
/// </summary>
16+
void Unsubscribe();
17+
}

src/Microsoft.AspNetCore.SystemWebAdapters/Ref.Standard.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ internal HttpContext() { }
5050
public System.Web.HttpServerUtility Server { get { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} }
5151
public System.Web.SessionState.HttpSessionState Session { get { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} }
5252
public System.Security.Principal.IPrincipal User { get { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} set { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} }
53+
public System.Web.ISubscriptionToken DisposeOnPipelineCompleted(System.IDisposable target) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
5354
object System.IServiceProvider.GetService(System.Type service) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
5455
}
5556
public partial class HttpContextBase : System.IServiceProvider
@@ -346,6 +347,11 @@ public sealed partial class HttpUnhandledException : System.Web.HttpException
346347
public HttpUnhandledException(string message) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
347348
public HttpUnhandledException(string message, System.Exception innerException) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
348349
}
350+
public partial interface ISubscriptionToken
351+
{
352+
bool IsActive { get; }
353+
void Unsubscribe();
354+
}
349355
public enum SameSiteMode
350356
{
351357
Lax = 1,

src/Microsoft.AspNetCore.SystemWebAdapters/TypeForwards.Framework.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.HttpSessionStateBase))]
2424
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.HttpSessionStateWrapper))]
2525
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.HttpUnhandledException))]
26+
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.ISubscriptionToken))]
2627
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.SameSiteMode))]
2728
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.Caching.Cache))]
2829
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.Caching.CacheDependency))]

test/Microsoft.AspNetCore.SystemWebAdapters.Tests/HttpContextTests.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,5 +152,93 @@ public void CacheFromServices()
152152
// Assert
153153
Assert.Same(cache, result);
154154
}
155+
156+
[Fact]
157+
public void DisposeOnPipelineCompleted()
158+
{
159+
// Arrange
160+
var coreContext = new Mock<HttpContextCore>();
161+
var coreResponse = new Mock<HttpResponseCore>();
162+
163+
coreContext.Setup(c => c.Response).Returns(coreResponse.Object);
164+
165+
var context = new HttpContext(coreContext.Object);
166+
var disposable = new Mock<IDisposable>();
167+
168+
// Act
169+
var token = context.DisposeOnPipelineCompleted(disposable.Object);
170+
171+
// Assert
172+
Assert.True(token.IsActive);
173+
}
174+
175+
[Fact]
176+
public void DisposeOnPipelineCompletedUnsubscribed()
177+
{
178+
// Arrange
179+
var coreContext = new Mock<HttpContextCore>();
180+
var coreResponse = new Mock<HttpResponseCore>();
181+
182+
coreContext.Setup(c => c.Response).Returns(coreResponse.Object);
183+
184+
var context = new HttpContext(coreContext.Object);
185+
var disposable = new Mock<IDisposable>();
186+
187+
// Act
188+
var token = context.DisposeOnPipelineCompleted(disposable.Object);
189+
190+
token.Unsubscribe();
191+
192+
// Assert
193+
Assert.False(token.IsActive);
194+
}
195+
196+
[Fact]
197+
public void DisposeOnPipelineCompletedUnsubscribedDisposed()
198+
{
199+
// Arrange
200+
IDisposable registeredDisposable = null!;
201+
202+
var coreContext = new Mock<HttpContextCore>();
203+
var coreResponse = new Mock<HttpResponseCore>();
204+
coreResponse.Setup(c => c.RegisterForDispose(It.IsAny<IDisposable>()))
205+
.Callback((IDisposable disposable) => registeredDisposable = disposable);
206+
207+
coreContext.Setup(c => c.Response).Returns(coreResponse.Object);
208+
209+
var context = new HttpContext(coreContext.Object);
210+
var disposable = new Mock<IDisposable>();
211+
212+
// Act
213+
var token = context.DisposeOnPipelineCompleted(disposable.Object);
214+
token.Unsubscribe();
215+
registeredDisposable.Dispose();
216+
217+
// Assert
218+
Assert.False(token.IsActive);
219+
disposable.Verify(d => d.Dispose(), Times.Never);
220+
}
221+
222+
[Fact]
223+
public void DisposeOnPipelineCompletedDisposed()
224+
{
225+
// Arrange
226+
var coreContext = new Mock<HttpContextCore>();
227+
var coreResponse = new Mock<HttpResponseCore>();
228+
coreResponse.Setup(c => c.RegisterForDispose(It.IsAny<IDisposable>()))
229+
.Callback((IDisposable disposable) => disposable.Dispose());
230+
231+
coreContext.Setup(c => c.Response).Returns(coreResponse.Object);
232+
233+
var context = new HttpContext(coreContext.Object);
234+
var disposable = new Mock<IDisposable>();
235+
236+
// Act
237+
var token = context.DisposeOnPipelineCompleted(disposable.Object);
238+
239+
// Assert
240+
Assert.False(token.IsActive);
241+
disposable.Verify(d => d.Dispose(), Times.Once);
242+
}
155243
}
156244
}

0 commit comments

Comments
 (0)