diff --git a/samples/RemoteAuth/Identity/MvcCoreApp/Controllers/TestController.cs b/samples/RemoteAuth/Identity/MvcCoreApp/Controllers/TestController.cs index cad5aa8218..389d8153c4 100644 --- a/samples/RemoteAuth/Identity/MvcCoreApp/Controllers/TestController.cs +++ b/samples/RemoteAuth/Identity/MvcCoreApp/Controllers/TestController.cs @@ -1,3 +1,4 @@ +using System.Web.SessionState; using ClassLibrary; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SystemWebAdapters; @@ -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); diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionAttribute.cs b/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionAttribute.cs index 0e83316f26..4a9152be24 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionAttribute.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionAttribute.cs @@ -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 { - 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; + } + } + } } diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionBehavior.cs b/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionBehavior.cs index b55c8eca07..a3f499aae9 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionBehavior.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionBehavior.cs @@ -5,6 +5,10 @@ namespace Microsoft.AspNetCore.SystemWebAdapters; +/// +/// Instead of this, please use . +/// +[Obsolete("Prefer System.Web.SessionState")] public enum SessionBehavior { /// diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionMiddleware.cs b/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionMiddleware.cs index cd6b027c72..f49cdb9554 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionMiddleware.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionMiddleware.cs @@ -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 _logger; + private readonly ILogger _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 logger) + public SessionLoadMiddleware(RequestDelegate next, ILogger logger) { _next = next; _logger = logger; } public Task InvokeAsync(HttpContextCore context) - => context.GetEndpoint()?.Metadata.GetMetadata() is { Behavior: not SessionBehavior.None } metadata - ? ManageStateAsync(context, metadata) + => context.Features.GetRequired() 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(); + 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(null); + feature.State = null; } } diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionStateMiddleware.cs b/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionStateMiddleware.cs new file mode 100644 index 0000000000..1ac73c5def --- /dev/null +++ b/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SessionStateMiddleware.cs @@ -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(new SessionStateFeature(context)); + await _next(context); + context.Features.Set(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(); + } +} diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SystemWebAdaptersExtensions.cs b/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SystemWebAdaptersExtensions.cs index 966d5e2205..78ce0fc840 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SystemWebAdaptersExtensions.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters.CoreServices/SystemWebAdaptersExtensions.cs @@ -106,7 +106,7 @@ public static void UseSystemWebAdapters(this IApplicationBuilder app) ApplicationEvent.PostUpdateRequestCache, }); - app.UseMiddleware(); + app.UseMiddleware(); if (app.AreHttpApplicationEventsRequired()) { @@ -146,6 +146,7 @@ public Action Configure(Action next) { builder.UseMiddleware(); builder.UseMiddleware(); + builder.UseMiddleware(); if (builder.AreHttpApplicationEventsRequired()) { diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Adapters/Features/ISessionStateFeature.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/Adapters/Features/ISessionStateFeature.cs new file mode 100644 index 0000000000..b006f85739 --- /dev/null +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/Adapters/Features/ISessionStateFeature.cs @@ -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; + +/// +/// Represents session state for System.Web +/// +[Experimental(Constants.ExperimentalFeatures.DiagnosticId)] +public interface ISessionStateFeature +{ + SessionStateBehavior Behavior { get; set; } + + bool IsPreLoad { get; } + + HttpSessionState? Session { get; } + + ISessionState? State { get; set; } +} +#endif diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Ref.Standard.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Ref.Standard.cs index c4e79b8abe..0e0dd4416d 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Ref.Standard.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/Ref.Standard.cs @@ -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 @@ -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 { @@ -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 { @@ -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, diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/TypeForwards.Framework.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/TypeForwards.Framework.cs index 8d529cc2d7..4ec96364a4 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/TypeForwards.Framework.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/Generated/TypeForwards.Framework.cs @@ -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))] diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs index 2a106c6c21..76f30ebedd 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContext.cs @@ -93,7 +93,10 @@ public IPrincipal User } } - public HttpSessionState? Session => _context.Features.Get(); + public HttpSessionState? Session => _context.Features.Get()?.Session; + + public void SetSessionStateBehavior(SessionStateBehavior sessionStateBehavior) + => _context.Features.GetRequired().Behavior = sessionStateBehavior; public DateTime Timestamp => _context.Features.GetRequired().Timestamp.DateTime; diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContextBase.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContextBase.cs index 136378b31a..0ac23f7165 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContextBase.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContextBase.cs @@ -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 @@ -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(); } } diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContextWrapper.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContextWrapper.cs index be4766cc25..5a7388547e 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContextWrapper.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/HttpContextWrapper.cs @@ -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); } } diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/SessionState/HttpSessionState.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/SessionState/HttpSessionState.cs index 73a2ff7212..b961716c99 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters/SessionState/HttpSessionState.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/SessionState/HttpSessionState.cs @@ -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; @@ -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 _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; diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters/SessionState/SessionStateBehavior.cs b/src/Microsoft.AspNetCore.SystemWebAdapters/SessionState/SessionStateBehavior.cs new file mode 100644 index 0000000000..52ceb19daa --- /dev/null +++ b/src/Microsoft.AspNetCore.SystemWebAdapters/SessionState/SessionStateBehavior.cs @@ -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 +}; diff --git a/test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/SessionState/SessionIntegrationTests.cs b/test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/SessionState/SessionIntegrationTests.cs new file mode 100644 index 0000000000..0c095d68d1 --- /dev/null +++ b/test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/SessionState/SessionIntegrationTests.cs @@ -0,0 +1,143 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using System.Web.SessionState; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests.SessionState; + +[Collection(nameof(SelfHostedTests))] +public class SessionIntegrationTests +{ + [InlineData("/?override=disabled", "Session:null")] + [InlineData("/?override=readonly", "ReadOnly:True")] + [InlineData("/?override=required", "ReadOnly:False")] + [InlineData("/?override=default", "Session:null")] + [Theory] + public async Task TestSetSessionStateBehavior(string endpoint, string expected) + { + var actual = await GetAsync(endpoint); + Assert.Equal(expected, actual); + } + + [InlineData("/disabled", "Session:null")] + [InlineData("/readonly", "ReadOnly:True")] + [InlineData("/required", "ReadOnly:False")] + [InlineData("/default", "Session:null")] + [Theory] + public async Task TestSessionAttribute(string endpoint, string expected) + { + var actual = await GetAsync(endpoint); + Assert.Equal(expected, actual); + } + + [InlineData("/disabled?override=required", "ReadOnly:False")] + [InlineData("/disabled?override=readonly", "ReadOnly:True")] + [InlineData("/readonly?override=required", "ReadOnly:False")] + [InlineData("/default?override=disabled", "Session:null")] + [Theory] + public async Task TestOverrideSessionStateBehavior(string endpoint, string expected) + { + var actual = await GetAsync(endpoint); + Assert.Equal(expected, actual); + } + + private static async Task GetAsync(string endpoint) + { + using var host = await new HostBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder + .UseTestServer(options => + { + options.AllowSynchronousIO = true; + }) + .ConfigureServices(services => + { + services.AddRouting(); + services.AddControllers(); + services.AddSystemWebAdapters() + .WrapAspNetCoreSession(); + services.AddDistributedMemoryCache(); + }) + .Configure(app => + { + app.UseRouting(); + app.Use((ctx, next) => + { + SetOverrideSessionBehavior(ctx); + return next(ctx); + }); + app.UseSession(); + app.UseSystemWebAdapters(); + app.UseEndpoints(endpoints => + { + endpoints.MapGet("/", (context) => GetSessionStatus(context)); + endpoints.MapGet("/disabled", (context) => GetSessionStatus(context)).WithMetadata(new SessionAttribute { SessionBehavior = SessionStateBehavior.Disabled }); + endpoints.MapGet("/readonly", (context) => GetSessionStatus(context)).WithMetadata(new SessionAttribute { SessionBehavior = SessionStateBehavior.ReadOnly }); + endpoints.MapGet("/required", (context) => GetSessionStatus(context)).WithMetadata(new SessionAttribute { SessionBehavior = SessionStateBehavior.Required }); + endpoints.MapGet("/default", (context) => GetSessionStatus(context)).WithMetadata(new SessionAttribute { SessionBehavior = SessionStateBehavior.Default }); + }); + }); + }) + .StartAsync(); + + var uri = new Uri(endpoint, UriKind.Relative); + + try + { + return await host.GetTestClient().GetStringAsync(uri).ConfigureAwait(false); + } + finally + { + await host.StopAsync(); + } + } + + private static void SetOverrideSessionBehavior(HttpContext context) + { + string? overrideValue = context.Request.QueryString["override"]; + + switch (overrideValue) + { + case "disabled": + context.SetSessionStateBehavior(SessionStateBehavior.Disabled); + break; + case "readonly": + context.SetSessionStateBehavior(SessionStateBehavior.ReadOnly); + break; + case "required": + context.SetSessionStateBehavior(SessionStateBehavior.Required); + break; + case "default": + context.SetSessionStateBehavior(SessionStateBehavior.Default); + break; + default: + break; + } + } + + private static Task GetSessionStatus(HttpContext context) + { + var session = context.Session; + if (session == null) + { + context.Response.Write("Session:null"); + } + else + { + context.Response.Write($"ReadOnly:{session.IsReadOnly}"); ; + } + + return Task.CompletedTask; + } +} diff --git a/test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/SessionState/Wrapped/AspNetCoreSessionState.cs b/test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/SessionState/Wrapped/AspNetCoreSessionStateTests.cs similarity index 97% rename from test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/SessionState/Wrapped/AspNetCoreSessionState.cs rename to test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/SessionState/Wrapped/AspNetCoreSessionStateTests.cs index 2f7d3f06fc..808e6c3ff7 100644 --- a/test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/SessionState/Wrapped/AspNetCoreSessionState.cs +++ b/test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/SessionState/Wrapped/AspNetCoreSessionStateTests.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using System.Web.SessionState; using AutoFixture; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.SystemWebAdapters.SessionState.Serialization; @@ -286,7 +287,9 @@ private static async Task CreateSessionStateFromSessionManager(Mo var options = Options.Create(sessionSerializerOptions); var aspNetCoreSessionManager = new AspNetCoreSessionManager(new Composite(serializer.Object), loggerFactory.Object, options); - return await aspNetCoreSessionManager.CreateAsync(httpContextCore.Object, new SessionAttribute() { IsReadOnly = isReadOnly }); + var behavior = isReadOnly ? SessionStateBehavior.ReadOnly : SessionStateBehavior.Required; + + return await aspNetCoreSessionManager.CreateAsync(httpContextCore.Object, new SessionAttribute() { SessionBehavior = behavior }); } private sealed class Composite : ICompositeSessionKeySerializer diff --git a/test/Microsoft.AspNetCore.SystemWebAdapters.Tests/HttpContextTests.cs b/test/Microsoft.AspNetCore.SystemWebAdapters.Tests/HttpContextTests.cs index 2570312e5e..74170b1779 100644 --- a/test/Microsoft.AspNetCore.SystemWebAdapters.Tests/HttpContextTests.cs +++ b/test/Microsoft.AspNetCore.SystemWebAdapters.Tests/HttpContextTests.cs @@ -501,5 +501,28 @@ public void SetHttpContextToNull() // Assert Assert.Null(HttpContext.Current); } + + [InlineData(SessionStateBehavior.Required)] + [InlineData(SessionStateBehavior.Default)] + [InlineData(SessionStateBehavior.Disabled)] + [InlineData(SessionStateBehavior.ReadOnly)] + [Theory] + public void SetSessionStateBehavior(SessionStateBehavior behavior) + { + // Arrange + var coreContext = new DefaultHttpContext(); + var context = new HttpContext(coreContext); + + var feature = new Mock(); + feature.SetupAllProperties(); + + coreContext.Features.Set(feature.Object); + + // Act + context.SetSessionStateBehavior(behavior); + + // Assert + feature.VerifySet(f => f.Behavior = behavior); + } } }