Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,33 @@ public IPrincipal User
return null;
}

public ISubscriptionToken DisposeOnPipelineCompleted(IDisposable target)
{
var token = new DisposeOnPipelineSubscriptionToken(target);
_context.Response.RegisterForDispose(token);
return token;
}

[return: NotNullIfNotNull("context")]
public static implicit operator HttpContext?(HttpContextCore? context) => context?.GetAdapter();

[return: NotNullIfNotNull("context")]
public static implicit operator HttpContextCore?(HttpContext? context) => context?._context;

private sealed class DisposeOnPipelineSubscriptionToken : ISubscriptionToken, IDisposable
{
private IDisposable? _other;

public DisposeOnPipelineSubscriptionToken(IDisposable other) => _other = other;

bool ISubscriptionToken.IsActive => _other is not null;

void ISubscriptionToken.Unsubscribe() => _other = null;

void IDisposable.Dispose()
{
_other?.Dispose();
_other = null;
}
}
}
17 changes: 17 additions & 0 deletions src/Microsoft.AspNetCore.SystemWebAdapters/ISubscriptionToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Web;

public interface ISubscriptionToken
{
/// <summary>
/// Returns a value stating whether the subscription is currently active
/// </summary>
bool IsActive { get; }

/// <summary>
/// Unsubscribes from the event
/// </summary>
void Unsubscribe();
}
6 changes: 6 additions & 0 deletions src/Microsoft.AspNetCore.SystemWebAdapters/Ref.Standard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ internal HttpContext() { }
public System.Web.HttpServerUtility Server { get { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} }
public System.Web.SessionState.HttpSessionState Session { get { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");} }
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");} }
public System.Web.ISubscriptionToken DisposeOnPipelineCompleted(System.IDisposable target) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
object System.IServiceProvider.GetService(System.Type service) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
}
public partial class HttpContextBase : System.IServiceProvider
Expand Down Expand Up @@ -346,6 +347,11 @@ public sealed partial class HttpUnhandledException : System.Web.HttpException
public HttpUnhandledException(string message) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
public HttpUnhandledException(string message, System.Exception innerException) { throw new System.PlatformNotSupportedException("Only support when running on ASP.NET Core or System.Web");}
}
public partial interface ISubscriptionToken
{
bool IsActive { get; }
void Unsubscribe();
}
public enum SameSiteMode
{
Lax = 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.HttpSessionStateBase))]
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.HttpSessionStateWrapper))]
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.HttpUnhandledException))]
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.ISubscriptionToken))]
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.SameSiteMode))]
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.Caching.Cache))]
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.Caching.CacheDependency))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,93 @@ public void CacheFromServices()
// Assert
Assert.Same(cache, result);
}

[Fact]
public void DisposeOnPipelineCompleted()
{
// Arrange
var coreContext = new Mock<HttpContextCore>();
var coreResponse = new Mock<HttpResponseCore>();

coreContext.Setup(c => c.Response).Returns(coreResponse.Object);

var context = new HttpContext(coreContext.Object);
var disposable = new Mock<IDisposable>();

// Act
var token = context.DisposeOnPipelineCompleted(disposable.Object);

// Assert
Assert.True(token.IsActive);
}

[Fact]
public void DisposeOnPipelineCompletedUnsubscribed()
{
// Arrange
var coreContext = new Mock<HttpContextCore>();
var coreResponse = new Mock<HttpResponseCore>();

coreContext.Setup(c => c.Response).Returns(coreResponse.Object);

var context = new HttpContext(coreContext.Object);
var disposable = new Mock<IDisposable>();

// Act
var token = context.DisposeOnPipelineCompleted(disposable.Object);

token.Unsubscribe();

// Assert
Assert.False(token.IsActive);
}

[Fact]
public void DisposeOnPipelineCompletedUnsubscribedDisposed()
{
// Arrange
IDisposable registeredDisposable = null!;

var coreContext = new Mock<HttpContextCore>();
var coreResponse = new Mock<HttpResponseCore>();
coreResponse.Setup(c => c.RegisterForDispose(It.IsAny<IDisposable>()))
.Callback((IDisposable disposable) => registeredDisposable = disposable);

coreContext.Setup(c => c.Response).Returns(coreResponse.Object);

var context = new HttpContext(coreContext.Object);
var disposable = new Mock<IDisposable>();

// Act
var token = context.DisposeOnPipelineCompleted(disposable.Object);
token.Unsubscribe();
registeredDisposable.Dispose();

// Assert
Assert.False(token.IsActive);
disposable.Verify(d => d.Dispose(), Times.Never);
}

[Fact]
public void DisposeOnPipelineCompletedDisposed()
{
// Arrange
var coreContext = new Mock<HttpContextCore>();
var coreResponse = new Mock<HttpResponseCore>();
coreResponse.Setup(c => c.RegisterForDispose(It.IsAny<IDisposable>()))
.Callback((IDisposable disposable) => disposable.Dispose());

coreContext.Setup(c => c.Response).Returns(coreResponse.Object);

var context = new HttpContext(coreContext.Object);
var disposable = new Mock<IDisposable>();

// Act
var token = context.DisposeOnPipelineCompleted(disposable.Object);

// Assert
Assert.False(token.IsActive);
disposable.Verify(d => d.Dispose(), Times.Once);
}
}
}