22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System ;
5+ using System . Buffers ;
56using System . IO ;
67using System . Linq ;
78using System . Net ;
89using System . Net . Http ;
10+ using System . Text . Json ;
11+ using System . Text . Json . Serialization ;
912using System . Threading ;
1013using System . Threading . Tasks ;
1114using Microsoft . AspNetCore . Http ;
1619
1720namespace Microsoft . AspNetCore . SystemWebAdapters . SessionState . RemoteSession ;
1821
19- internal partial class RemoteAppSessionStateManager : ISessionManager
22+ internal abstract partial class RemoteAppSessionStateManager : ISessionManager
2023{
21- private readonly ISessionSerializer _serializer ;
22- private readonly ILogger < RemoteAppSessionStateManager > _logger ;
23- private readonly RemoteAppSessionStateClientOptions _options ;
24- private readonly HttpClient _backchannelClient ;
24+ private readonly ILogger _logger ;
2525
2626 public RemoteAppSessionStateManager (
2727 ISessionSerializer serializer ,
2828 IOptions < RemoteAppSessionStateClientOptions > options ,
2929 IOptions < RemoteAppClientOptions > remoteAppClientOptions ,
30- ILogger < RemoteAppSessionStateManager > logger )
30+ ILogger logger )
3131 {
32- _options = options ? . Value ?? throw new ArgumentNullException ( nameof ( options ) ) ;
33- _backchannelClient = remoteAppClientOptions ? . Value . BackchannelClient ?? throw new ArgumentNullException ( nameof ( remoteAppClientOptions ) ) ;
32+ Options = options ? . Value ?? throw new ArgumentNullException ( nameof ( options ) ) ;
33+ BackchannelClient = remoteAppClientOptions ? . Value . BackchannelClient ?? throw new ArgumentNullException ( nameof ( remoteAppClientOptions ) ) ;
3434 _logger = logger ?? throw new ArgumentNullException ( nameof ( logger ) ) ;
35- _serializer = serializer ?? throw new ArgumentNullException ( nameof ( serializer ) ) ;
35+ Serializer = serializer ?? throw new ArgumentNullException ( nameof ( serializer ) ) ;
3636 }
3737
38+ protected ISessionSerializer Serializer { get ; }
39+
40+ protected RemoteAppSessionStateClientOptions Options { get ; }
41+
42+ protected HttpClient BackchannelClient { get ; }
43+
3844 [ LoggerMessage ( EventId = 0 , Level = LogLevel . Debug , Message = "Loaded {Count} items from remote session state for session {SessionId}" ) ]
39- private partial void LogSessionLoad ( int count , string sessionId ) ;
45+ protected partial void LogSessionLoad ( int count , string sessionId ) ;
4046
4147 [ LoggerMessage ( EventId = 1 , Level = LogLevel . Error , Message = "Unable to load remote session state for session {SessionId}" ) ]
42- private partial void LogRemoteSessionException ( Exception exc , string ? sessionId ) ;
48+ protected partial void LogRemoteSessionException ( Exception exc , string ? sessionId ) ;
4349
4450 [ LoggerMessage ( EventId = 2 , Level = LogLevel . Trace , Message = "Received {StatusCode} response getting remote session state" ) ]
45- private partial void LogRetrieveResponse ( HttpStatusCode statusCode ) ;
51+ protected partial void LogRetrieveResponse ( HttpStatusCode statusCode ) ;
4652
4753 [ LoggerMessage ( EventId = 3 , Level = LogLevel . Trace , Message = "Received {StatusCode} response committing remote session state" ) ]
48- private partial void LogCommitResponse ( HttpStatusCode statusCode ) ;
54+ protected partial void LogCommitResponse ( HttpStatusCode statusCode ) ;
4955
5056 public async Task < ISessionState > CreateAsync ( HttpContextCore context , SessionAttribute metadata )
5157 {
5258 // If an existing remote session ID is present in the request, use its session ID.
5359 // Otherwise, leave session ID null for now; it will be provided by the remote service
5460 // when session data is loaded.
55- var sessionId = context . Request . Cookies [ _options . CookieName ] ;
61+ var sessionId = context . Request . Cookies [ Options . CookieName ] ;
5662
5763 try
5864 {
@@ -70,103 +76,24 @@ public async Task<ISessionState> CreateAsync(HttpContextCore context, SessionAtt
7076 }
7177 }
7278
73- private async Task < ISessionState > GetSessionDataAsync ( string ? sessionId , bool readOnly , HttpContextCore callingContext , CancellationToken token )
74- {
75- // The request message is manually disposed at a later time
76- #pragma warning disable CA2000 // Dispose objects before losing scope
77- var req = new HttpRequestMessage ( HttpMethod . Get , _options . Path . Relative ) ;
78- #pragma warning restore CA2000 // Dispose objects before losing scope
79-
80- AddSessionCookieToHeader ( req , sessionId ) ;
81- AddReadOnlyHeader ( req , readOnly ) ;
82-
83- var response = await _backchannelClient . SendAsync ( req , HttpCompletionOption . ResponseHeadersRead , token ) ;
84-
85- LogRetrieveResponse ( response . StatusCode ) ;
86-
87- response . EnsureSuccessStatusCode ( ) ;
88-
89- var remoteSessionState = await _serializer . DeserializeAsync ( await response . Content . ReadAsStreamAsync ( token ) , token ) ;
90-
91- if ( remoteSessionState is null )
92- {
93- throw new InvalidOperationException ( "Could not retrieve remote session state" ) ;
94- }
95-
96- // Propagate headers back to the caller since a new session ID may have been set
97- // by the remote app if there was no session active previously or if the previous
98- // session expired.
99- PropagateHeaders ( response , callingContext , HeaderNames . SetCookie ) ;
100-
101- if ( remoteSessionState . IsReadOnly )
102- {
103- response . Dispose ( ) ;
104- return remoteSessionState ;
105- }
106-
107- return new RemoteSessionState ( remoteSessionState , response , SetOrReleaseSessionData ) ;
108- }
109-
110- /// <summary>
111- /// Commits changes to the server. Passing null <paramref name="state"/> will release the session lock but not update session data.
112- /// </summary>
113- private async Task SetOrReleaseSessionData ( ISessionState ? state , CancellationToken cancellationToken )
114- {
115- using var req = new HttpRequestMessage ( HttpMethod . Put , _options . Path . Relative ) ;
116-
117- if ( state is not null )
118- {
119- AddSessionCookieToHeader ( req , state . SessionID ) ;
120- req . Content = new SerializedSessionHttpContent ( _serializer , state ) ;
121- }
122-
123- using var response = await _backchannelClient . SendAsync ( req , cancellationToken ) ;
79+ protected abstract Task < ISessionState > GetSessionDataAsync ( string ? sessionId , bool readOnly , HttpContextCore callingContext , CancellationToken token ) ;
12480
125- LogCommitResponse ( response . StatusCode ) ;
126-
127- response . EnsureSuccessStatusCode ( ) ;
128- }
129-
130- private static void PropagateHeaders ( HttpResponseMessage responseMessage , HttpContextCore context , string cookieName )
81+ protected static void PropagateHeaders ( HttpResponseMessage responseMessage , HttpContextCore context , string cookieName )
13182 {
13283 if ( context ? . Response is not null && responseMessage . Headers . TryGetValues ( cookieName , out var cookieValues ) )
13384 {
13485 context . Response . Headers . AppendList ( cookieName , cookieValues . ToArray ( ) ) ;
13586 }
13687 }
13788
138- private void AddSessionCookieToHeader ( HttpRequestMessage req , string ? sessionId )
89+ protected void AddSessionCookieToHeader ( HttpRequestMessage req , string ? sessionId )
13990 {
14091 if ( ! string . IsNullOrEmpty ( sessionId ) )
14192 {
142- req . Headers . Add ( HeaderNames . Cookie , $ "{ _options . CookieName } ={ sessionId } ") ;
93+ req . Headers . Add ( HeaderNames . Cookie , $ "{ Options . CookieName } ={ sessionId } ") ;
14394 }
14495 }
14596
146- private static void AddReadOnlyHeader ( HttpRequestMessage req , bool readOnly )
97+ protected static void AddReadOnlyHeader ( HttpRequestMessage req , bool readOnly )
14798 => req . Headers . Add ( SessionConstants . ReadOnlyHeaderName , readOnly . ToString ( ) ) ;
148-
149- private class SerializedSessionHttpContent : HttpContent
150- {
151- private readonly ISessionSerializer _serializer ;
152- private readonly ISessionState _state ;
153-
154- public SerializedSessionHttpContent ( ISessionSerializer serializer , ISessionState state )
155- {
156- _serializer = serializer ;
157- _state = state ;
158- }
159-
160- protected override Task SerializeToStreamAsync ( Stream stream , TransportContext ? context )
161- => SerializeToStreamAsync ( stream , context , default ) ;
162-
163- protected override Task SerializeToStreamAsync ( Stream stream , TransportContext ? context , CancellationToken cancellationToken )
164- => _serializer . SerializeAsync ( _state , stream , cancellationToken ) ;
165-
166- protected override bool TryComputeLength ( out long length )
167- {
168- length = 0 ;
169- return false ;
170- }
171- }
17299}
0 commit comments