diff --git a/samples/SessionSample/Properties/launchSettings.json b/samples/SessionSample/Properties/launchSettings.json index 9137a2c..6bab3d3 100644 --- a/samples/SessionSample/Properties/launchSettings.json +++ b/samples/SessionSample/Properties/launchSettings.json @@ -12,13 +12,13 @@ "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { - "Hosting:Environment": "Development" + "ASPNETCORE_ENVIRONMENT": "Development" } }, "SessionSample": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "http://localhost:5000/", + "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/samples/SessionSample/Startup.cs b/samples/SessionSample/Startup.cs index 8946d5e..8c65358 100644 --- a/samples/SessionSample/Startup.cs +++ b/samples/SessionSample/Startup.cs @@ -19,6 +19,9 @@ public Startup(ILoggerFactory loggerFactory) public void ConfigureServices(IServiceCollection services) { + // Adds a default in-memory implementation of IDistributedCache + services.AddDistributedMemoryCache(); + // Uncomment the following line to use the Microsoft SQL Server implementation of IDistributedCache. // Note that this would require setting up the session state database. //services.AddSqlServerCache(o => @@ -32,10 +35,6 @@ public void ConfigureServices(IServiceCollection services) // This will override any previously registered IDistributedCache service. //services.AddSingleton(); #endif - // Adds a default in-memory implementation of IDistributedCache - services.AddMemoryCache(); - services.AddDistributedMemoryCache(); - services.AddSession(o => { o.IdleTimeout = TimeSpan.FromSeconds(10); diff --git a/src/Microsoft.AspNetCore.Session/DistributedSession.cs b/src/Microsoft.AspNetCore.Session/DistributedSession.cs index 1a638ff..6025423 100644 --- a/src/Microsoft.AspNetCore.Session/DistributedSession.cs +++ b/src/Microsoft.AspNetCore.Session/DistributedSession.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using System.Security.Cryptography; -using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Distributed; @@ -26,10 +25,11 @@ public class DistributedSession : ISession private readonly string _sessionKey; private readonly TimeSpan _idleTimeout; private readonly Func _tryEstablishSession; - private readonly IDictionary _store; private readonly ILogger _logger; + private IDictionary _store; private bool _isModified; private bool _loaded; + private bool _isAvailable; private bool _isNewSessionKey; private string _sessionId; private byte[] _sessionIdBytes; @@ -71,11 +71,20 @@ public class DistributedSession : ISession _isNewSessionKey = isNewSessionKey; } + public bool IsAvailable + { + get + { + Load(); + return _isAvailable; + } + } + public string Id { get { - Load(); // TODO: Silent failure + Load(); if (_sessionId == null) { _sessionId = new Guid(IdBytes).ToString(); @@ -88,8 +97,7 @@ private byte[] IdBytes { get { - Load(); // TODO: Silent failure - if (_sessionIdBytes == null) + if (IsAvailable && _sessionIdBytes == null) { _sessionIdBytes = new byte[IdByteCount]; CryptoRandom.GetBytes(_sessionIdBytes); @@ -102,14 +110,14 @@ public IEnumerable Keys { get { - Load(); // TODO: Silent failure + Load(); return _store.Keys.Select(key => key.KeyString); } } public bool TryGetValue(string key, out byte[] value) { - Load(); // TODO: Silent failure + Load(); return _store.TryGetValue(new EncodedKey(key), out value); } @@ -120,22 +128,24 @@ public void Set(string key, byte[] value) throw new ArgumentNullException(nameof(value)); } - var encodedKey = new EncodedKey(key); - if (encodedKey.KeyBytes.Length > KeyLengthLimit) + if (IsAvailable) { - throw new ArgumentOutOfRangeException(nameof(key), - Resources.FormatException_KeyLengthIsExceeded(KeyLengthLimit)); - } + var encodedKey = new EncodedKey(key); + if (encodedKey.KeyBytes.Length > KeyLengthLimit) + { + throw new ArgumentOutOfRangeException(nameof(key), + Resources.FormatException_KeyLengthIsExceeded(KeyLengthLimit)); + } - Load(); - if (!_tryEstablishSession()) - { - throw new InvalidOperationException(Resources.Exception_InvalidSessionEstablishment); + if (!_tryEstablishSession()) + { + throw new InvalidOperationException(Resources.Exception_InvalidSessionEstablishment); + } + _isModified = true; + byte[] copy = new byte[value.Length]; + Buffer.BlockCopy(src: value, srcOffset: 0, dst: copy, dstOffset: 0, count: value.Length); + _store[encodedKey] = copy; } - _isModified = true; - byte[] copy = new byte[value.Length]; - Buffer.BlockCopy(src: value, srcOffset: 0, dst: copy, dstOffset: 0, count: value.Length); - _store[encodedKey] = copy; } public void Remove(string key) @@ -155,21 +165,35 @@ private void Load() { if (!_loaded) { - var data = _cache.Get(_sessionKey); - if (data != null) + try { - Deserialize(new MemoryStream(data)); + var data = _cache.Get(_sessionKey); + if (data != null) + { + Deserialize(new MemoryStream(data)); + } + else if (!_isNewSessionKey) + { + _logger.AccessingExpiredSession(_sessionKey); + } + _isAvailable = true; } - else if (!_isNewSessionKey) + catch (Exception exception) { - _logger.AccessingExpiredSession(_sessionKey); + _logger.SessionCacheReadException(_sessionKey, exception); + _isAvailable = false; + _sessionId = string.Empty; + _sessionIdBytes = null; + _store = new NoOpSessionStore(); + } + finally + { + _loaded = true; } - _loaded = true; } } - // TODO: This should throw if called directly, but most other places it should fail silently - // (e.g. TryGetValue should just return null). + // This will throw if called directly and a failure occurs. The user is expected to handle the failures. public async Task LoadAsync() { if (!_loaded) @@ -183,6 +207,7 @@ public async Task LoadAsync() { _logger.AccessingExpiredSession(_sessionKey); } + _isAvailable = true; _loaded = true; } } @@ -191,24 +216,32 @@ public async Task CommitAsync() { if (_isModified) { - var data = await _cache.GetAsync(_sessionKey); - if (_logger.IsEnabled(LogLevel.Information) && data == null) + if (_logger.IsEnabled(LogLevel.Information)) { - _logger.SessionStarted(_sessionKey, Id); + try + { + var data = await _cache.GetAsync(_sessionKey); + if (data == null) + { + _logger.SessionStarted(_sessionKey, Id); + } + } + catch (Exception exception) + { + _logger.SessionCacheReadException(_sessionKey, exception); + } } - _isModified = false; var stream = new MemoryStream(); Serialize(stream); + await _cache.SetAsync( _sessionKey, stream.ToArray(), new DistributedCacheEntryOptions().SetSlidingExpiration(_idleTimeout)); - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.SessionStored(_sessionKey, Id, _store.Count); - } + _isModified = false; + _logger.SessionStored(_sessionKey, Id, _store.Count); } else { @@ -245,7 +278,6 @@ private void Deserialize(Stream content) { if (content == null || content.ReadByte() != SerializationRevision) { - // TODO: Throw? // Replace the un-readable format. _isModified = true; return; @@ -333,77 +365,5 @@ private byte[] ReadBytes(Stream stream, int count) return output; } - // Keys are stored in their utf-8 encoded state. - // This saves us from de-serializing and re-serializing every key on every request. - private class EncodedKey - { - private string _keyString; - private int? _hashCode; - - internal EncodedKey(string key) - { - _keyString = key; - KeyBytes = Encoding.UTF8.GetBytes(key); - } - - public EncodedKey(byte[] key) - { - KeyBytes = key; - } - - internal string KeyString - { - get - { - if (_keyString == null) - { - _keyString = Encoding.UTF8.GetString(KeyBytes, 0, KeyBytes.Length); - } - return _keyString; - } - } - - internal byte[] KeyBytes { get; private set; } - - public override bool Equals(object obj) - { - var otherKey = obj as EncodedKey; - if (otherKey == null) - { - return false; - } - if (KeyBytes.Length != otherKey.KeyBytes.Length) - { - return false; - } - if (_hashCode.HasValue && otherKey._hashCode.HasValue - && _hashCode.Value != otherKey._hashCode.Value) - { - return false; - } - for (int i = 0; i < KeyBytes.Length; i++) - { - if (KeyBytes[i] != otherKey.KeyBytes[i]) - { - return false; - } - } - return true; - } - - public override int GetHashCode() - { - if (!_hashCode.HasValue) - { - _hashCode = SipHash.GetHashCode(KeyBytes); - } - return _hashCode.Value; - } - - public override string ToString() - { - return KeyString; - } - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Session/DistributedSessionStore.cs b/src/Microsoft.AspNetCore.Session/DistributedSessionStore.cs index da8f6cc..180599b 100644 --- a/src/Microsoft.AspNetCore.Session/DistributedSessionStore.cs +++ b/src/Microsoft.AspNetCore.Session/DistributedSessionStore.cs @@ -29,14 +29,6 @@ public DistributedSessionStore(IDistributedCache cache, ILoggerFactory loggerFac _loggerFactory = loggerFactory; } - public bool IsAvailable - { - get - { - return true; // TODO: - } - } - public ISession Create(string sessionKey, TimeSpan idleTimeout, Func tryEstablishSession, bool isNewSessionKey) { if (string.IsNullOrEmpty(sessionKey)) diff --git a/src/Microsoft.AspNetCore.Session/EncodedKey.cs b/src/Microsoft.AspNetCore.Session/EncodedKey.cs new file mode 100644 index 0000000..ac16954 --- /dev/null +++ b/src/Microsoft.AspNetCore.Session/EncodedKey.cs @@ -0,0 +1,80 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Text; + +namespace Microsoft.AspNetCore.Session +{ + // Keys are stored in their utf-8 encoded state. + // This saves us from de-serializing and re-serializing every key on every request. + internal class EncodedKey + { + private string _keyString; + private int? _hashCode; + + internal EncodedKey(string key) + { + _keyString = key; + KeyBytes = Encoding.UTF8.GetBytes(key); + } + + public EncodedKey(byte[] key) + { + KeyBytes = key; + } + + internal string KeyString + { + get + { + if (_keyString == null) + { + _keyString = Encoding.UTF8.GetString(KeyBytes, 0, KeyBytes.Length); + } + return _keyString; + } + } + + internal byte[] KeyBytes { get; private set; } + + public override bool Equals(object obj) + { + var otherKey = obj as EncodedKey; + if (otherKey == null) + { + return false; + } + if (KeyBytes.Length != otherKey.KeyBytes.Length) + { + return false; + } + if (_hashCode.HasValue && otherKey._hashCode.HasValue + && _hashCode.Value != otherKey._hashCode.Value) + { + return false; + } + for (int i = 0; i < KeyBytes.Length; i++) + { + if (KeyBytes[i] != otherKey.KeyBytes[i]) + { + return false; + } + } + return true; + } + + public override int GetHashCode() + { + if (!_hashCode.HasValue) + { + _hashCode = SipHash.GetHashCode(KeyBytes); + } + return _hashCode.Value; + } + + public override string ToString() + { + return KeyString; + } + } +} diff --git a/src/Microsoft.AspNetCore.Session/ISessionStore.cs b/src/Microsoft.AspNetCore.Session/ISessionStore.cs index e62a0eb..8831a60 100644 --- a/src/Microsoft.AspNetCore.Session/ISessionStore.cs +++ b/src/Microsoft.AspNetCore.Session/ISessionStore.cs @@ -8,8 +8,6 @@ namespace Microsoft.AspNetCore.Session { public interface ISessionStore { - bool IsAvailable { get; } - ISession Create(string sessionKey, TimeSpan idleTimeout, Func tryEstablishSession, bool isNewSessionKey); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Session/LoggingExtensions.cs b/src/Microsoft.AspNetCore.Session/LoggingExtensions.cs index f2bb1be..c6780a7 100644 --- a/src/Microsoft.AspNetCore.Session/LoggingExtensions.cs +++ b/src/Microsoft.AspNetCore.Session/LoggingExtensions.cs @@ -12,6 +12,7 @@ internal static class LoggingExtensions private static Action _sessionStarted; private static Action _sessionLoaded; private static Action _sessionStored; + private static Action _sessionCacheReadException; private static Action _errorUnprotectingCookie; static LoggingExtensions() @@ -23,7 +24,7 @@ static LoggingExtensions() _accessingExpiredSession = LoggerMessage.Define( eventId: 2, logLevel: LogLevel.Warning, - formatString: "Accessing expired session; Key:{sessionKey}"); + formatString: "Accessing expired session, Key:{sessionKey}"); _sessionStarted = LoggerMessage.Define( eventId: 3, logLevel: LogLevel.Information, @@ -36,8 +37,12 @@ static LoggingExtensions() eventId: 5, logLevel: LogLevel.Debug, formatString: "Session stored; Key:{sessionKey}, Id:{sessionId}, Count:{count}"); - _errorUnprotectingCookie = LoggerMessage.Define( + _sessionCacheReadException = LoggerMessage.Define( eventId: 6, + logLevel: LogLevel.Error, + formatString: "Session cache read exception, Key:{sessionKey}"); + _errorUnprotectingCookie = LoggerMessage.Define( + eventId: 7, logLevel: LogLevel.Warning, formatString: "Error unprotecting the session cookie."); } @@ -67,6 +72,11 @@ public static void SessionStored(this ILogger logger, string sessionKey, string _sessionStored(logger, sessionKey, sessionId, count, null); } + public static void SessionCacheReadException(this ILogger logger, string sessionKey, Exception exception) + { + _sessionCacheReadException(logger, sessionKey, exception); + } + public static void ErrorUnprotectingSessionCookie(this ILogger logger, Exception exception) { _errorUnprotectingCookie(logger, exception); diff --git a/src/Microsoft.AspNetCore.Session/NoOpSessionStore.cs b/src/Microsoft.AspNetCore.Session/NoOpSessionStore.cs new file mode 100644 index 0000000..6a89ad3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Session/NoOpSessionStore.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.AspNetCore.Session +{ + internal class NoOpSessionStore : IDictionary + { + public byte[] this[EncodedKey key] + { + get + { + return null; + } + + set + { + + } + } + + public int Count { get; } = 0; + + public bool IsReadOnly { get; } = false; + + public ICollection Keys { get; } = new EncodedKey[0]; + + public ICollection Values { get; } = new byte[0][]; + + public void Add(KeyValuePair item) { } + + public void Add(EncodedKey key, byte[] value) { } + + public void Clear() { } + + public bool Contains(KeyValuePair item) => false; + + public bool ContainsKey(EncodedKey key) => false; + + public void CopyTo(KeyValuePair[] array, int arrayIndex) { } + + public IEnumerator> GetEnumerator() => Enumerable.Empty>().GetEnumerator(); + + public bool Remove(KeyValuePair item) => false; + + public bool Remove(EncodedKey key) => false; + + public bool TryGetValue(EncodedKey key, out byte[] value) + { + value = null; + return false; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs b/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs index f59805e..ed7cf6c 100644 --- a/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs +++ b/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs @@ -40,7 +40,6 @@ public async Task ReadingEmptySessionDoesNotCreateCookie() }) .ConfigureServices(services => { - services.AddMemoryCache(); services.AddDistributedMemoryCache(); services.AddSession(); }); @@ -72,7 +71,6 @@ public async Task SettingAValueCausesTheCookieToBeCreated() }) .ConfigureServices(services => { - services.AddMemoryCache(); services.AddDistributedMemoryCache(); services.AddSession(); }); @@ -111,9 +109,7 @@ public async Task SessionCanBeAccessedOnTheNextRequest() }) .ConfigureServices(services => { - services.AddMemoryCache(); services.AddDistributedMemoryCache(); - services.AddSession(); }); @@ -166,9 +162,7 @@ public async Task RemovedItemCannotBeAccessedAgain() .ConfigureServices( services => { - services.AddMemoryCache(); services.AddDistributedMemoryCache(); - services.AddSession(); }); @@ -219,9 +213,7 @@ public async Task ClearedItemsCannotBeAccessedAgain() }) .ConfigureServices(services => { - services.AddMemoryCache(); services.AddDistributedMemoryCache(); - services.AddSession(); }); @@ -258,10 +250,7 @@ public async Task SessionStart_LogsInformation() .ConfigureServices(services => { services.AddSingleton(typeof(ILoggerFactory), loggerFactory); - - services.AddMemoryCache(); services.AddDistributedMemoryCache(); - services.AddSession(); }); @@ -310,10 +299,7 @@ public async Task ExpiredSession_LogsWarning() .ConfigureServices(services => { services.AddSingleton(typeof(ILoggerFactory), loggerFactory); - - services.AddMemoryCache(); services.AddDistributedMemoryCache(); - services.AddSession(o => o.IdleTimeout = TimeSpan.FromMilliseconds(30)); }); @@ -373,10 +359,7 @@ public async Task RefreshesSession_WhenSessionData_IsNotModified() .ConfigureServices(services => { services.AddSingleton(typeof(ILoggerFactory), new NullLoggerFactory()); - - services.AddMemoryCache(); services.AddDistributedMemoryCache(); - services.AddSession(o => o.IdleTimeout = TimeSpan.FromMinutes(20)); services.Configure(o => o.Clock = clock); }); @@ -426,9 +409,7 @@ public async Task SessionFeature_IsUnregistered_WhenResponseGoingOut() }) .ConfigureServices(services => { - services.AddMemoryCache(); services.AddDistributedMemoryCache(); - services.AddSession(); }); @@ -471,9 +452,7 @@ public async Task SessionFeature_IsUnregistered_WhenResponseGoingOut_AndAnUnhand }) .ConfigureServices(services => { - services.AddMemoryCache(); services.AddDistributedMemoryCache(); - services.AddSession(); }); @@ -502,9 +481,7 @@ public async Task SessionKeys_AreCaseSensitive() }) .ConfigureServices(services => { - services.AddMemoryCache(); services.AddDistributedMemoryCache(); - services.AddSession(); }); @@ -516,61 +493,188 @@ public async Task SessionKeys_AreCaseSensitive() } } - private class TestClock : ISystemClock + [Fact] + public async Task SessionLogsCacheReadException() { - public TestClock() + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink, enabled: true); + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseSession(); + app.Run(context => + { + byte[] value; + Assert.False(context.Session.TryGetValue("key", out value)); + Assert.Equal(null, value); + Assert.Equal(string.Empty, context.Session.Id); + Assert.False(context.Session.Keys.Any()); + return Task.FromResult(0); + }); + }) + .ConfigureServices(services => + { + services.AddSingleton(typeof(ILoggerFactory), loggerFactory); + services.AddSingleton(new UnreliableCache(new MemoryCache(new MemoryCacheOptions())) + { + DisableGet = true + }); + services.AddSession(); + }); + + using (var server = new TestServer(builder)) { - UtcNow = new DateTimeOffset(2013, 1, 1, 1, 0, 0, TimeSpan.Zero); - } + var client = server.CreateClient(); + var response = await client.GetAsync(string.Empty); + response.EnsureSuccessStatusCode(); - public DateTimeOffset UtcNow { get; private set; } + var sessionLogMessages = sink.Writes.OnlyMessagesFromSource().ToArray(); - public void Add(TimeSpan timespan) - { - UtcNow = UtcNow.Add(timespan); + Assert.Equal(1, sessionLogMessages.Length); + Assert.Contains("Session cache read exception", sessionLogMessages[0].State.ToString()); + Assert.Equal(LogLevel.Error, sessionLogMessages[0].LogLevel); } } - private class TestDistributedCache : IDistributedCache + [Fact] + public async Task SessionLogsCacheWriteException() { - public byte[] Get(string key) + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink, enabled: true); + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseSession(); + app.Run(context => + { + context.Session.SetInt32("key", 0); + return Task.FromResult(0); + }); + }) + .ConfigureServices(services => + { + services.AddSingleton(typeof(ILoggerFactory), loggerFactory); + services.AddSingleton(new UnreliableCache(new MemoryCache(new MemoryCacheOptions())) + { + DisableSetAsync = true + }); + services.AddSession(); + }); + + using (var server = new TestServer(builder)) { - throw new NotImplementedException(); + var client = server.CreateClient(); + var response = await client.GetAsync(string.Empty); + response.EnsureSuccessStatusCode(); + + var sessionLogMessages = sink.Writes.OnlyMessagesFromSource().ToArray(); + + Assert.Equal(1, sessionLogMessages.Length); + Assert.Contains("Session started", sessionLogMessages[0].State.ToString()); + Assert.Equal(LogLevel.Information, sessionLogMessages[0].LogLevel); + + var sessionMiddlewareLogMessages = sink.Writes.OnlyMessagesFromSource().ToArray(); + Assert.Equal(1, sessionMiddlewareLogMessages.Length); + Assert.Contains("Error closing the session.", sessionMiddlewareLogMessages[0].State.ToString()); + Assert.Equal(LogLevel.Error, sessionMiddlewareLogMessages[0].LogLevel); } + } - public Task GetAsync(string key) + [Fact] + public async Task SessionLogsCacheRefreshException() + { + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink, enabled: true); + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseSession(); + app.Run(context => + { + // The middleware calls context.Session.CommitAsync() once per request + return Task.FromResult(0); + }); + }) + .ConfigureServices(services => + { + services.AddSingleton(typeof(ILoggerFactory), loggerFactory); + services.AddSingleton(new UnreliableCache(new MemoryCache(new MemoryCacheOptions())) + { + DisableRefreshAsync = true + }); + services.AddSession(); + }); + + using (var server = new TestServer(builder)) { - throw new NotImplementedException(); + var client = server.CreateClient(); + var response = await client.GetAsync(string.Empty); + response.EnsureSuccessStatusCode(); + + var sessionLogMessages = sink.Writes.OnlyMessagesFromSource().ToArray(); + + Assert.Equal(1, sessionLogMessages.Length); + Assert.Contains("Error closing the session.", sessionLogMessages[0].State.ToString()); + Assert.Equal(LogLevel.Error, sessionLogMessages[0].LogLevel); } + } - public void Refresh(string key) + private class TestClock : ISystemClock + { + public TestClock() { - throw new NotImplementedException(); + UtcNow = new DateTimeOffset(2013, 1, 1, 1, 0, 0, TimeSpan.Zero); } - public Task RefreshAsync(string key) + public DateTimeOffset UtcNow { get; private set; } + + public void Add(TimeSpan timespan) { - throw new NotImplementedException(); + UtcNow = UtcNow.Add(timespan); } + } + + private class UnreliableCache : IDistributedCache + { + private readonly MemoryDistributedCache _cache; - public void Remove(string key) + public bool DisableGet { get; set; } + public bool DisableSetAsync { get; set; } + public bool DisableRefreshAsync { get; set; } + + public UnreliableCache(IMemoryCache memoryCache) { - throw new NotImplementedException(); + _cache = new MemoryDistributedCache(memoryCache); } - public Task RemoveAsync(string key) + public byte[] Get(string key) { - throw new NotImplementedException(); + if (DisableGet) + { + throw new InvalidOperationException(); + } + return _cache.Get(key); } - - public void Set(string key, byte[] value, DistributedCacheEntryOptions options) + public Task GetAsync(string key) => _cache.GetAsync(key); + public void Refresh(string key) => _cache.Refresh(key); + public Task RefreshAsync(string key) { - throw new NotImplementedException(); + if (DisableRefreshAsync) + { + throw new InvalidOperationException(); + } + return _cache.RefreshAsync(key); } - + public void Remove(string key) => _cache.Remove(key); + public Task RemoveAsync(string key) => _cache.RemoveAsync(key); + public void Set(string key, byte[] value, DistributedCacheEntryOptions options) => _cache.Set(key, value, options); public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options) { - throw new NotImplementedException(); + if (DisableSetAsync) + { + throw new InvalidOperationException(); + } + return _cache.SetAsync(key, value, options); } } }