Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Web.SessionState;
using ClassLibrary;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SystemWebAdapters;
Expand All @@ -9,7 +10,7 @@ namespace MvcCoreApp.Controllers
public class TestController : Controller
{
[HttpGet]
[Session(IsReadOnly = true)]
[Session(SessionBehavior = SessionStateBehavior.ReadOnly)]
[Route("/api/test/request/info")]
public void Get([FromQuery] bool? suppress = false) => RequestInfo.WriteRequestInfo(suppress ?? false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,51 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Web.SessionState;

namespace Microsoft.AspNetCore.SystemWebAdapters;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class SessionAttribute : Attribute
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Clounea could you add some tests for this? other than that, I'm good with these changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I could. I will also add tests for overriding scenario

{
public SessionBehavior Behavior { get; set; } = SessionBehavior.Preload;
[Obsolete("Prefer SessionBehavior instead.")]
public SessionBehavior Behavior
{
get
{
if (SessionBehavior is SessionStateBehavior.Disabled)
{
return SystemWebAdapters.SessionBehavior.None;
}

public bool IsReadOnly { get; set; }
return IsPreLoad ? SystemWebAdapters.SessionBehavior.Preload : SystemWebAdapters.SessionBehavior.OnDemand;
}
set
{
SessionBehavior = value switch
{
SystemWebAdapters.SessionBehavior.None => SessionStateBehavior.Disabled,
SystemWebAdapters.SessionBehavior.Preload => SessionStateBehavior.Required,
SystemWebAdapters.SessionBehavior.OnDemand => SessionStateBehavior.Required,
_ => throw new ArgumentOutOfRangeException(nameof(value)),
};
}
}

public SessionStateBehavior SessionBehavior { get; set; }

public bool IsPreLoad { get; set; } = true;

public bool IsReadOnly
{
get => SessionBehavior is SessionStateBehavior.ReadOnly;
[Obsolete("Prefer SessionBehavior property")]
set
{
if (value)
{
SessionBehavior = SessionStateBehavior.ReadOnly;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

namespace Microsoft.AspNetCore.SystemWebAdapters;

/// <summary>
/// Instead of this, please use <see cref="System.Web.SessionState.SessionStateBehavior"/>.
/// </summary>
[Obsolete("Prefer System.Web.SessionState")]
public enum SessionBehavior
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,65 +6,65 @@
using System.Threading.Tasks;
using System.Web.SessionState;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SystemWebAdapters.Features;
using Microsoft.AspNetCore.SystemWebAdapters.SessionState;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Microsoft.AspNetCore.SystemWebAdapters;
internal partial class SessionMiddleware
internal partial class SessionLoadMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<SessionMiddleware> _logger;
private readonly ILogger<SessionLoadMiddleware> _logger;

[LoggerMessage(EventId = 0, Level = LogLevel.Trace, Message = "Initializing session state: {Behavior}")]
private partial void LogMessage(SessionBehavior behavior);
private partial void LogMessage(SessionStateBehavior behavior);

[LoggerMessage(EventId = 1, Level = LogLevel.Warning, Message = "Creating session on demand by synchronously waiting on a potential asynchronous connection")]
private partial void LogOnDemand();

private readonly TimeSpan CommitTimeout = TimeSpan.FromMinutes(1);

public SessionMiddleware(RequestDelegate next, ILogger<SessionMiddleware> logger)
public SessionLoadMiddleware(RequestDelegate next, ILogger<SessionLoadMiddleware> logger)
{
_next = next;
_logger = logger;
}

public Task InvokeAsync(HttpContextCore context)
=> context.GetEndpoint()?.Metadata.GetMetadata<SessionAttribute>() is { Behavior: not SessionBehavior.None } metadata
? ManageStateAsync(context, metadata)
=> context.Features.GetRequired<ISessionStateFeature>() is { Behavior: not SessionStateBehavior.Disabled and not SessionStateBehavior.Default } feature
? ManageStateAsync(context, feature)
: _next(context);

private async Task ManageStateAsync(HttpContextCore context, SessionAttribute metadata)
private async Task ManageStateAsync(HttpContextCore context, ISessionStateFeature feature)
{
LogMessage(metadata.Behavior);
LogMessage(feature.Behavior);

var manager = context.RequestServices.GetRequiredService<ISessionManager>();
var details = new SessionAttribute { SessionBehavior = feature.Behavior, IsPreLoad = feature.IsPreLoad };

using var state = metadata.Behavior switch
{
using var state = !feature.IsPreLoad
#pragma warning disable CA2000 // False positive for CA2000 here
#pragma warning disable CS0618 // Type or member is obsolete
SessionBehavior.OnDemand => new LazySessionState(context, LogOnDemand, metadata, manager),
#pragma warning restore CS0618 // Type or member is obsolete
? new LazySessionState(context, LogOnDemand, details, manager)
#pragma warning restore CA2000 // Dispose objects before losing scope
: await manager.CreateAsync(context, details);

SessionBehavior.Preload => await manager.CreateAsync(context, metadata),
var behavior => throw new InvalidOperationException($"Unknown session behavior {behavior}"),
};

context.Features.Set(new HttpSessionState(state));
feature.State = state;

try
{
await _next(context);

using var cts = new CancellationTokenSource(CommitTimeout);
await state.CommitAsync(cts.Token);

if (!details.IsReadOnly)
{
await state.CommitAsync(cts.Token);
}
}
finally
{
context.Features.Set<HttpSessionState?>(null);
feature.State = null;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading.Tasks;
using System.Web.SessionState;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SystemWebAdapters.Features;
using Microsoft.AspNetCore.SystemWebAdapters.SessionState;

namespace Microsoft.AspNetCore.SystemWebAdapters;

internal sealed class SessionStateMiddleware
{
private readonly RequestDelegate _next;

public SessionStateMiddleware(RequestDelegate next) => _next = next;

public async Task InvokeAsync(HttpContext context)
{
context.Features.Set<ISessionStateFeature>(new SessionStateFeature(context));
await _next(context);
context.Features.Set<ISessionStateFeature>(null);
}

private sealed class SessionStateFeature : ISessionStateFeature
{
private readonly HttpContext _context;
private SessionStateBehavior? _behavior;
private HttpSessionState? _session;

public SessionStateFeature(HttpContext context)
{
_context = context;
}

public SessionStateBehavior Behavior
{
get => _behavior is { } behavior ? behavior : GetExisting()?.SessionBehavior ?? default;
set => _behavior = value;
}

public HttpSessionState? Session => State is null ? null : _session ??= new(this);

public ISessionState? State { get; set; }

public bool IsPreLoad => GetExisting()?.IsPreLoad ?? true;

private SessionAttribute? GetExisting()
=> _context.GetEndpoint()?.Metadata.GetMetadata<SessionAttribute>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public static void UseSystemWebAdapters(this IApplicationBuilder app)
ApplicationEvent.PostUpdateRequestCache,
});

app.UseMiddleware<SessionMiddleware>();
app.UseMiddleware<SessionLoadMiddleware>();

if (app.AreHttpApplicationEventsRequired())
{
Expand Down Expand Up @@ -146,6 +146,7 @@ public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
builder.UseMiddleware<SetHttpContextTimestampMiddleware>();
builder.UseMiddleware<RegisterAdapterFeaturesMiddleware>();
builder.UseMiddleware<SessionStateMiddleware>();

if (builder.AreHttpApplicationEventsRequired())
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if NETCOREAPP
using System.Diagnostics.CodeAnalysis;
using System.Web;
using System.Web.SessionState;
using Microsoft.AspNetCore.SystemWebAdapters.SessionState;

namespace Microsoft.AspNetCore.SystemWebAdapters.Features;

/// <summary>
/// Represents session state for System.Web
/// </summary>
[Experimental(Constants.ExperimentalFeatures.DiagnosticId)]
public interface ISessionStateFeature
{
SessionStateBehavior Behavior { get; set; }

bool IsPreLoad { get; }

HttpSessionState? Session { get; }

ISessionState? State { get; set; }
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ internal HttpContext() { }
public void RewritePath(string path, bool rebaseClientPath) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void RewritePath(string filePath, string pathInfo, string queryString) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void RewritePath(string filePath, string pathInfo, string queryString, bool setClientFilePath) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior sessionStateBehavior) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
object System.IServiceProvider.GetService(System.Type service) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
}
public partial class HttpContextBase : System.IServiceProvider
Expand Down Expand Up @@ -185,6 +186,7 @@ public partial class HttpContextBase : System.IServiceProvider
public virtual void RewritePath(string path, bool rebaseClientPath) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public virtual void RewritePath(string filePath, string pathInfo, string queryString) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public virtual void RewritePath(string filePath, string pathInfo, string queryString, bool setClientFilePath) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public virtual void SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior sessionStateBehavior) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
}
public partial class HttpContextWrapper : System.Web.HttpContextBase
{
Expand All @@ -210,6 +212,7 @@ public partial class HttpContextWrapper : System.Web.HttpContextBase
public override void RewritePath(string path, bool rebaseClientPath) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public override void RewritePath(string filePath, string pathInfo, string queryString) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public override void RewritePath(string filePath, string pathInfo, string queryString, bool setClientFilePath) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public override void SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior sessionStateBehavior) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
}
public sealed partial class HttpCookie
{
Expand Down Expand Up @@ -855,6 +858,13 @@ internal HttpSessionState() { }
public void Remove(string name) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void RemoveAll() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
}
public enum SessionStateBehavior
{
Default = 0,
Disabled = 3,
ReadOnly = 2,
Required = 1,
}
public enum SessionStateMode
{
Custom = 4,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.Configuration.HttpCapabilitiesBase))]
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.Hosting.HostingEnvironment))]
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.SessionState.HttpSessionState))]
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.SessionState.SessionStateBehavior))]
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.SessionState.SessionStateMode))]
5 changes: 4 additions & 1 deletion src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ public IPrincipal User
}
}

public HttpSessionState? Session => _context.Features.Get<HttpSessionState>();
public HttpSessionState? Session => _context.Features.Get<ISessionStateFeature>()?.Session;

public void SetSessionStateBehavior(SessionStateBehavior sessionStateBehavior)
=> _context.Features.GetRequired<ISessionStateFeature>().Behavior = sessionStateBehavior;

public DateTime Timestamp => _context.Features.GetRequired<ITimestampFeature>().Timestamp.DateTime;

Expand Down
3 changes: 3 additions & 0 deletions src/Microsoft.AspNetCore.SystemWebAdapters/HttpContextBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections;
using System.Security.Principal;
using System.Diagnostics.CodeAnalysis;
using System.Web.SessionState;
using Microsoft.AspNetCore.SystemWebAdapters;

namespace System.Web
Expand Down Expand Up @@ -67,5 +68,7 @@ public virtual IPrincipal User
public virtual void RewritePath(string filePath, string pathInfo, string? queryString) => throw new NotImplementedException();

public virtual void RewritePath(string filePath, string pathInfo, string? queryString, bool setClientFilePath) => throw new NotImplementedException();

public virtual void SetSessionStateBehavior(SessionStateBehavior sessionStateBehavior) => throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,7 @@ public override IPrincipal User
public override void RewritePath(string filePath, string pathInfo, string? queryString) => _context.RewritePath(filePath, pathInfo, queryString);

public override void RewritePath(string filePath, string pathInfo, string? queryString, bool setClientFilePath) => _context.RewritePath(filePath, pathInfo, queryString, setClientFilePath);

public override void SetSessionStateBehavior(SessionStateBehavior sessionStateBehavior) => _context.SetSessionStateBehavior(sessionStateBehavior);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using Microsoft.AspNetCore.SystemWebAdapters.Features;
using Microsoft.AspNetCore.SystemWebAdapters.SessionState;

namespace System.Web.SessionState;
Expand All @@ -10,12 +11,19 @@ namespace System.Web.SessionState;
[Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix", Justification = Constants.ApiFromAspNet)]
public class HttpSessionState : ICollection
{
private readonly Func<ISessionState> _state;

internal HttpSessionState(ISessionStateFeature feature)
{
_state = () => feature.State ?? throw new InvalidOperationException("Session state is no longer available");
}

public HttpSessionState(ISessionState container)
{
State = container;
_state = () => container;
}

internal ISessionState State { get; }
internal ISessionState State => _state();

public string SessionID => State.SessionID;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// 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.SessionState;

public enum SessionStateBehavior
{
Default = 0,
Required = 1,
ReadOnly = 2,
Disabled = 3
};
Loading