This is an automated email from the ASF dual-hosted git repository.

bbender pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode-native.git


The following commit(s) were added to refs/heads/develop by this push:
     new f379f6d  GEODE-9359: Add NetCore SessionState support (#834)
f379f6d is described below

commit f379f6dd546c916be4d7f9e66c9842ff05448fb9
Author: Michael Martell <[email protected]>
AuthorDate: Thu Aug 12 08:25:36 2021 -0700

    GEODE-9359: Add NetCore SessionState support (#834)
    
    - fix formatting
    - externalize log-level and log-file
    - use auto implemented properties
    - Use var everywhere
    - Don't use private assets
---
 .../NetCore.Session.IntegrationTests.csproj        |  23 ++
 .../Properties/launchSettings.json                 |   8 +
 .../SessionStateIntegrationTests.cs                | 175 ++++++++++++
 .../GeodeCacheServiceCollectionExtensions.cs       |  40 +++
 .../GeodeSessionStateCacheOptions.cs               |  31 +++
 netcore/NetCore.Session/NetCore.Session.csproj     |  17 ++
 netcore/NetCore.Session/NetCoreSessionState.cs     | 299 +++++++++++++++++++++
 netcore/geode-dotnet-core.sln                      |  18 ++
 8 files changed, 611 insertions(+)

diff --git 
a/netcore/NetCore.Session.IntegrationTests/NetCore.Session.IntegrationTests.csproj
 
b/netcore/NetCore.Session.IntegrationTests/NetCore.Session.IntegrationTests.csproj
new file mode 100644
index 0000000..e0420cc
--- /dev/null
+++ 
b/netcore/NetCore.Session.IntegrationTests/NetCore.Session.IntegrationTests.csproj
@@ -0,0 +1,23 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+
+    <IsPackable>false</IsPackable>
+
+    <Platforms>x64</Platforms>
+  </PropertyGroup>
+
+  <ItemGroup>
+       <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
+       <PackageReference Include="xunit" Version="2.4.1" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+    <PackageReference Include="coverlet.collector" Version="1.2.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\NetCore.Session\NetCore.Session.csproj" />
+    <ProjectReference Include="..\NetCore\NetCore.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git 
a/netcore/NetCore.Session.IntegrationTests/Properties/launchSettings.json 
b/netcore/NetCore.Session.IntegrationTests/Properties/launchSettings.json
new file mode 100644
index 0000000..5d4ca80
--- /dev/null
+++ b/netcore/NetCore.Session.IntegrationTests/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+  "profiles": {
+       "Apache.Geode.Session.IntegrationTests": {
+         "commandName": "Project",
+         "nativeDebugging": true
+       }
+  }
+}
diff --git 
a/netcore/NetCore.Session.IntegrationTests/SessionStateIntegrationTests.cs 
b/netcore/NetCore.Session.IntegrationTests/SessionStateIntegrationTests.cs
new file mode 100644
index 0000000..d34002c
--- /dev/null
+++ b/netcore/NetCore.Session.IntegrationTests/SessionStateIntegrationTests.cs
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using System;
+using System.Text;
+using Xunit;
+using Apache.Geode.Client;
+using System.Linq;
+using Microsoft.Extensions.Caching.Distributed;
+using System.Threading.Tasks;
+
+namespace Apache.Geode.Session.IntegrationTests {
+  public class SessionStateIntegrationTests {
+
+    [Fact]
+    public void SetGet() {
+      var ssCacheOptions = new GeodeSessionStateCacheOptions();
+      ssCacheOptions.Host = "localhost";
+      ssCacheOptions.Port = 10334;
+      ssCacheOptions.RegionName = "exampleRegion";
+
+      using var ssCache = new GeodeSessionStateCache(ssCacheOptions);
+
+      var options = new DistributedCacheEntryOptions();
+      var localTime = DateTime.Now.AddDays(1);
+      var dateAndOffset =
+          new DateTimeOffset(localTime, 
TimeZoneInfo.Local.GetUtcOffset(localTime));
+      options.AbsoluteExpiration = dateAndOffset;
+      var testValue = new byte[] { 1, 2, 3, 4, 5 };
+      ssCache.Set("testKey", testValue, options);
+      var value = ssCache.Get("testKey");
+      Assert.True(testValue.SequenceEqual(value));
+    }
+
+    [Fact]
+    public void Refresh() {
+      var ssCacheOptions = new GeodeSessionStateCacheOptions();
+      ssCacheOptions.Host = "localhost";
+      ssCacheOptions.Port = 10334;
+      ssCacheOptions.RegionName = "exampleRegion";
+
+      using var ssCache = new GeodeSessionStateCache(ssCacheOptions);
+
+      var options = new DistributedCacheEntryOptions();
+      var numSeconds = 20;
+      options.SlidingExpiration = new TimeSpan(0, 0, numSeconds);
+      var testValue = new byte[] { 1, 2, 3, 4, 5 };
+
+      // Set a value
+      ssCache.Set("testKey", testValue, options);
+
+      // Wait half a timeout then refresh
+      System.Threading.Thread.Sleep(numSeconds / 2 * 1000);
+      ssCache.Refresh("testKey");
+
+      // Wait beyond the original expiration
+      System.Threading.Thread.Sleep(numSeconds / 2 * 1000 + 1);
+
+      // Ensure it's not expired
+      var value = ssCache.Get("testKey");
+      Assert.True(testValue.SequenceEqual(value));
+    }
+
+    [Fact]
+    public void SetWithAbsoluteExpiration() {
+      var ssCacheOptions = new GeodeSessionStateCacheOptions();
+      ssCacheOptions.Host = "localhost";
+      ssCacheOptions.Port = 10334;
+      ssCacheOptions.RegionName = "exampleRegion";
+
+      using var ssCache = new GeodeSessionStateCache(ssCacheOptions);
+
+      var options = new DistributedCacheEntryOptions();
+      options.AbsoluteExpiration = DateTime.Now.AddSeconds(5);
+      ssCache.Set("testKey", Encoding.UTF8.GetBytes("testValue"), options);
+      System.Threading.Thread.Sleep(6000);
+      var value = ssCache.Get("testKey");
+      Assert.Null(value);
+    }
+
+    [Fact]
+    public void Remove() {
+      var ssCacheOptions = new GeodeSessionStateCacheOptions();
+      ssCacheOptions.Host = "localhost";
+      ssCacheOptions.Port = 10334;
+      ssCacheOptions.RegionName = "exampleRegion";
+
+      using var ssCache = new GeodeSessionStateCache(ssCacheOptions);
+
+      var options = new DistributedCacheEntryOptions();
+      var localTime = DateTime.Now.AddDays(1);
+      var dateAndOffset =
+          new DateTimeOffset(localTime, 
TimeZoneInfo.Local.GetUtcOffset(localTime));
+      options.AbsoluteExpiration = dateAndOffset;
+      var testValue = new byte[] { 1, 2, 3, 4, 5 };
+      ssCache.Set("testKey", testValue, options);
+      var value = ssCache.Get("testKey");
+
+      ssCache.Remove("testKey");
+      value = ssCache.Get("testKey");
+      Assert.Null(value);
+    }
+
+    [Fact]
+    public void SetGetRemoveAsync() {
+      var ssCacheOptions = new GeodeSessionStateCacheOptions();
+      ssCacheOptions.Host = "localhost";
+      ssCacheOptions.Port = 10334;
+      ssCacheOptions.RegionName = "exampleRegion";
+
+      using var ssCache = new GeodeSessionStateCache(ssCacheOptions);
+
+      var options = new DistributedCacheEntryOptions();
+      var localTime = DateTime.Now.AddDays(1);
+      var dateAndOffset =
+          new DateTimeOffset(localTime, 
TimeZoneInfo.Local.GetUtcOffset(localTime));
+      options.AbsoluteExpiration = dateAndOffset;
+
+      var testValue1 = new byte[] { 1, 2, 3, 4, 5 };
+      var testValue2 = new byte[] { 11, 12, 13, 14, 15 };
+      var testValue3 = new byte[] { 21, 22, 23, 24, 25 };
+      var testValue4 = new byte[] { 31, 32, 33, 34, 35 };
+      var testValue5 = new byte[] { 41, 42, 43, 44, 45 };
+
+      var set1 = ssCache.SetAsync("testKey1", testValue1, options);
+      var set2 = ssCache.SetAsync("testKey2", testValue2, options);
+      var set3 = ssCache.SetAsync("testKey3", testValue3, options);
+      var set4 = ssCache.SetAsync("testKey4", testValue4, options);
+      var set5 = ssCache.SetAsync("testKey5", testValue5, options);
+
+      Task.WaitAll(set1, set2, set3, set4, set5);
+
+      var value1 = ssCache.GetAsync("testKey1");
+      var value2 = ssCache.GetAsync("testKey2");
+      var value3 = ssCache.GetAsync("testKey3");
+      var value4 = ssCache.GetAsync("testKey4");
+      var value5 = ssCache.GetAsync("testKey5");
+
+      Task.WaitAll(value1, value2, value3, value4, value5);
+
+      Assert.True(testValue1.SequenceEqual(value1.Result));
+      Assert.True(testValue2.SequenceEqual(value2.Result));
+      Assert.True(testValue3.SequenceEqual(value3.Result));
+      Assert.True(testValue4.SequenceEqual(value4.Result));
+      Assert.True(testValue5.SequenceEqual(value5.Result));
+
+      var rm1 = ssCache.RemoveAsync("testKey1");
+      var rm2 = ssCache.RemoveAsync("testKey2");
+      var rm3 = ssCache.RemoveAsync("testKey3");
+      var rm4 = ssCache.RemoveAsync("testKey4");
+      var rm5 = ssCache.RemoveAsync("testKey5");
+
+      Task.WaitAll(rm1, rm2, rm3, rm4, rm5);
+
+      Assert.Null(ssCache.Get("testKey1"));
+      Assert.Null(ssCache.Get("testKey2"));
+      Assert.Null(ssCache.Get("testKey3"));
+      Assert.Null(ssCache.Get("testKey4"));
+      Assert.Null(ssCache.Get("testKey5"));
+    }
+  }
+}
diff --git a/netcore/NetCore.Session/GeodeCacheServiceCollectionExtensions.cs 
b/netcore/NetCore.Session/GeodeCacheServiceCollectionExtensions.cs
new file mode 100644
index 0000000..113d079
--- /dev/null
+++ b/netcore/NetCore.Session/GeodeCacheServiceCollectionExtensions.cs
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using System;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Apache.Geode.Session {
+  public static class GeodeCacheServiceCollectionExtensions {
+    public static IServiceCollection AddGeodeSessionStateCache(
+        this IServiceCollection services, 
Action<GeodeSessionStateCacheOptions> setupAction) {
+      if (services == null) {
+        throw new ArgumentNullException(nameof(services));
+      }
+
+      if (setupAction == null) {
+        throw new ArgumentNullException(nameof(setupAction));
+      }
+
+      services.AddOptions();
+      services.Add(ServiceDescriptor.Singleton<IDistributedCache, 
GeodeSessionStateCache>());
+      services.Configure(setupAction);
+
+      return services;
+    }
+  }
+}
diff --git a/netcore/NetCore.Session/GeodeSessionStateCacheOptions.cs 
b/netcore/NetCore.Session/GeodeSessionStateCacheOptions.cs
new file mode 100644
index 0000000..d07e068
--- /dev/null
+++ b/netcore/NetCore.Session/GeodeSessionStateCacheOptions.cs
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using Microsoft.Extensions.Options;
+
+namespace Apache.Geode.Session {
+  public class GeodeSessionStateCacheOptions : 
IOptions<GeodeSessionStateCacheOptions> {
+    public string RegionName { get; set; }
+    public string Host { get; set; }
+    public int Port { get; set; }
+    public string LogLevel { get; set; } = "none";
+    public string LogFile { get; set; } = "GeodeSessionStateCache.log";
+
+    GeodeSessionStateCacheOptions 
IOptions<GeodeSessionStateCacheOptions>.Value {
+      get { return this; }
+    }
+  }
+}
diff --git a/netcore/NetCore.Session/NetCore.Session.csproj 
b/netcore/NetCore.Session/NetCore.Session.csproj
new file mode 100644
index 0000000..29e13a3
--- /dev/null
+++ b/netcore/NetCore.Session/NetCore.Session.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <Platforms>x64</Platforms>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" 
Version="3.1.3" />
+    <PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.3" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\NetCore\NetCore.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/netcore/NetCore.Session/NetCoreSessionState.cs 
b/netcore/NetCore.Session/NetCoreSessionState.cs
new file mode 100644
index 0000000..95b0434
--- /dev/null
+++ b/netcore/NetCore.Session/NetCoreSessionState.cs
@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using Apache.Geode.Client;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Apache.Geode.Session {
+  public class GeodeSessionStateValue {
+    public GeodeSessionStateValue() {}
+    public GeodeSessionStateValue(byte[] value) {
+      FromByteArray(value);
+    }
+
+    public byte[] Value { get; set; }
+    public DateTime LastAccessTimeUtc { get; set; }
+    public DateTime ExpirationTimeUtc { get; set; } = DateTime.MinValue;
+    public TimeSpan SpanUntilStale { get; set; } = TimeSpan.Zero;
+
+    public byte[] ToByteArray() {
+      var neededBytes = 3 * sizeof(long) + Value.Length;
+      var byteArray = new byte[neededBytes];
+      var byteIndex = 0;
+
+      Array.Copy(BitConverter.GetBytes(LastAccessTimeUtc.Ticks), 0, byteArray, 
byteIndex,
+                 sizeof(long));
+      byteIndex += sizeof(long);
+
+      Array.Copy(BitConverter.GetBytes(ExpirationTimeUtc.Ticks), 0, byteArray, 
byteIndex,
+                 sizeof(long));
+      byteIndex += sizeof(long);
+
+      Array.Copy(BitConverter.GetBytes(SpanUntilStale.Ticks), 0, byteArray, 
byteIndex,
+                 sizeof(long));
+      byteIndex += sizeof(long);
+
+      Array.Copy(Value, 0, byteArray, byteIndex, Value.Length);
+      return byteArray;
+    }
+
+    public void FromByteArray(byte[] data) {
+      var byteIndex = 0;
+
+      LastAccessTimeUtc = DateTime.FromBinary(BitConverter.ToInt64(data, 
byteIndex));
+      byteIndex += sizeof(long);
+
+      ExpirationTimeUtc = DateTime.FromBinary(BitConverter.ToInt64(data, 
byteIndex));
+      byteIndex += sizeof(long);
+
+      SpanUntilStale = TimeSpan.FromTicks(BitConverter.ToInt64(data, 
byteIndex));
+      byteIndex += sizeof(long);
+
+      Value = new byte[data.Length - byteIndex];
+      Array.Copy(data, byteIndex, Value, 0, data.Length - byteIndex);
+    }
+  }
+
+  public class GeodeSessionStateCache : GeodeNativeObject, IDistributedCache {
+    private readonly IGeodeCache _cache;
+    private ILogger<GeodeSessionStateCache> _logger;
+    private static Region _region;
+    private string _logLevel;
+    private string _logFile;
+    private string _regionName;
+    private readonly SemaphoreSlim _connectLock = new 
SemaphoreSlim(initialCount: 1, maxCount: 1);
+
+    public GeodeSessionStateCache(IOptions<GeodeSessionStateCacheOptions> 
optionsAccessor) {
+      var host = optionsAccessor.Value.Host;
+      var port = optionsAccessor.Value.Port;
+      _regionName = optionsAccessor.Value.RegionName;
+      _logLevel = optionsAccessor.Value.LogLevel;
+      _logFile = optionsAccessor.Value.LogFile;
+
+      _cache = CacheFactory.Create()
+                   .SetProperty("log-level", _logLevel)
+                   .SetProperty("log-file", _logFile)
+                   .CreateCache();
+
+      _cache.PoolManager.CreatePoolFactory().AddLocator(host, 
port).CreatePool("pool");
+
+      var regionFactory = _cache.CreateRegionFactory(RegionShortcut.Proxy);
+      _region = regionFactory.CreateRegion(_regionName);
+    }
+
+    // Returns the SessionStateValue for key, or null if key doesn't exist
+    public GeodeSessionStateValue GetValueForKey(string key) {
+      var cacheValue = _region.GetByteArray(key);
+
+      if (cacheValue != null) {
+        return new GeodeSessionStateValue(cacheValue);
+      } else
+        return null;
+    }
+
+    public byte[] Get(string key) {
+      if (key == null) {
+        throw new ArgumentNullException(nameof(key));
+      }
+
+      Connect();
+
+      // Check for nonexistent key
+      var ssValue = GetValueForKey(key);
+      if (ssValue == null) {
+        return null;
+      }
+
+      // Check for expired key
+      var nowUtc = DateTime.UtcNow;
+      if (ssValue.ExpirationTimeUtc != DateTime.MinValue && 
ssValue.ExpirationTimeUtc < nowUtc) {
+        return null;
+      }
+
+      // Check for stale key
+      if (ssValue.SpanUntilStale != TimeSpan.Zero &&
+          nowUtc > (ssValue.LastAccessTimeUtc + ssValue.SpanUntilStale)) {
+        return null;
+      }
+
+      // Update the times for sliding expirations
+      if (ssValue.SpanUntilStale != TimeSpan.Zero) {
+        ssValue.LastAccessTimeUtc = nowUtc;
+        _region.PutByteArray(key, ssValue.ToByteArray());
+      }
+
+      return ssValue.Value;
+    }
+
+    public Task<byte[]> GetAsync(string key, CancellationToken token = 
default(CancellationToken)) {
+      if (key == null) {
+        throw new ArgumentNullException(nameof(key));
+      }
+
+      token.ThrowIfCancellationRequested();
+
+      return Task.Factory.StartNew(() => Get(key), token);
+    }
+
+    public void Refresh(string key) {
+      if (key == null) {
+        throw new ArgumentNullException(nameof(key));
+      }
+
+      Connect();
+
+      // Check for nonexistent key
+      var ssValue = GetValueForKey(key);
+      if (ssValue == null) {
+        return;
+      }
+
+      // Check for expired key
+      var nowUtc = DateTime.UtcNow;
+      if (ssValue.ExpirationTimeUtc != DateTime.MinValue && 
ssValue.ExpirationTimeUtc < nowUtc) {
+        return;
+      }
+
+      // Check for stale key
+      if (ssValue.SpanUntilStale != TimeSpan.Zero &&
+          nowUtc > (ssValue.LastAccessTimeUtc + ssValue.SpanUntilStale)) {
+        return;
+      }
+
+      // Update the times for sliding expirations
+      if (ssValue.SpanUntilStale != TimeSpan.Zero) {
+        ssValue.LastAccessTimeUtc = nowUtc;
+        _region.PutByteArray(key, ssValue.ToByteArray());
+      }
+    }
+
+    public Task RefreshAsync(string key, CancellationToken token = 
default(CancellationToken)) {
+      if (key == null) {
+        throw new ArgumentNullException(nameof(key));
+      }
+
+      token.ThrowIfCancellationRequested();
+
+      return Task.Factory.StartNew(() => Refresh(key), token);
+    }
+
+    public void Remove(string key) {
+      if (key == null) {
+        throw new ArgumentNullException(nameof(key));
+      }
+
+      Connect();
+
+      _region.Remove(key);
+    }
+
+    public Task RemoveAsync(string key, CancellationToken token = 
default(CancellationToken)) {
+      if (key == null) {
+        throw new ArgumentNullException(nameof(key));
+      }
+
+      token.ThrowIfCancellationRequested();
+
+      return Task.Factory.StartNew(() => Remove(key), token);
+    }
+
+    public void Set(string key, byte[] value, DistributedCacheEntryOptions 
options) {
+      if (key == null) {
+        throw new ArgumentNullException(nameof(key));
+      }
+
+      if (value == null) {
+        throw new ArgumentNullException(nameof(value));
+      }
+
+      if (options == null) {
+        throw new ArgumentNullException(nameof(options));
+      }
+
+      Connect();
+
+      var ssValue = new GeodeSessionStateValue();
+      ssValue.Value = value;
+
+      var nowUtc = DateTime.UtcNow;
+      ssValue.LastAccessTimeUtc = nowUtc;
+
+      // No need to check stale or expired data when setting an absolute 
expiration.
+      // Think of if as setting a new key/value pair. Expired data will always 
be cleaned up
+      // when the CleanupExpiredData job runs.
+
+      if (options.AbsoluteExpiration != null) {
+        var dto = options.AbsoluteExpiration.Value;
+        ssValue.ExpirationTimeUtc = dto.DateTime + dto.Offset;
+      }
+
+      // If AbsoluteExpiration and AbsoluteExpirationRelativeToNow are set, 
use the latter.
+      if (options.AbsoluteExpirationRelativeToNow != null) {
+        var ts = options.AbsoluteExpirationRelativeToNow.Value;
+        ssValue.ExpirationTimeUtc = nowUtc + ts;
+      }
+
+      if (options.SlidingExpiration != null) {
+        ssValue.SpanUntilStale = options.SlidingExpiration.Value;
+      }
+
+      _region.PutByteArray(key, ssValue.ToByteArray());
+      return;
+    }
+
+    public Task SetAsync(string key, byte[] value, 
DistributedCacheEntryOptions options,
+                         CancellationToken token = default(CancellationToken)) 
{
+      if (key == null) {
+        throw new ArgumentNullException(nameof(key));
+      }
+
+      token.ThrowIfCancellationRequested();
+
+      return Task.Factory.StartNew(() => Set(key, value, options), token);
+    }
+
+    private void Connect() {
+      if (_region != null) {
+        return;
+      }
+
+      _connectLock.Wait();
+      try {
+        using var regionFactory = 
_cache.CreateRegionFactory(RegionShortcut.Proxy);
+        try {
+          _logger?.LogTrace("Create CacheRegion");
+          _region = regionFactory.CreateRegion(_regionName);
+          _logger?.LogTrace("CacheRegion created");
+        } catch (Exception e) {
+          _logger?.LogInformation(e, "Create CacheRegion failed... now trying 
to get the region");
+        }
+      } finally {
+        // regionFactory?.Dispose();
+        _connectLock.Release();
+      }
+    }
+
+    protected override void DestroyContainedObject() {
+      _region?.Dispose();
+      _region = null;
+    }
+  }
+}
diff --git a/netcore/geode-dotnet-core.sln b/netcore/geode-dotnet-core.sln
index 830ad88..164413c 100644
--- a/netcore/geode-dotnet-core.sln
+++ b/netcore/geode-dotnet-core.sln
@@ -7,6 +7,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCore", 
"NetCore\NetCore.
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCore.Test", 
"NetCore.Test\NetCore.Test.csproj", "{501DEA7E-8985-42A8-8BC9-C073E1B6DFE0}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Session", "Session", 
"{520C96EC-F929-4365-8D78-CC5785419B62}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCore.Session", 
"NetCore.Session\NetCore.Session.csproj", 
"{B88C58EB-B144-403B-85F7-7A5B45E643E3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = 
"NetCore.Session.IntegrationTests", 
"NetCore.Session.IntegrationTests\NetCore.Session.IntegrationTests.csproj", 
"{94D2CD59-A5F3-4504-BF01-0A3B95CE12B5}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|x64 = Debug|x64
@@ -21,10 +27,22 @@ Global
                {501DEA7E-8985-42A8-8BC9-C073E1B6DFE0}.Debug|x64.Build.0 = 
Debug|x64
                {501DEA7E-8985-42A8-8BC9-C073E1B6DFE0}.Release|x64.ActiveCfg = 
Release|x64
                {501DEA7E-8985-42A8-8BC9-C073E1B6DFE0}.Release|x64.Build.0 = 
Release|x64
+               {B88C58EB-B144-403B-85F7-7A5B45E643E3}.Debug|x64.ActiveCfg = 
Debug|x64
+               {B88C58EB-B144-403B-85F7-7A5B45E643E3}.Debug|x64.Build.0 = 
Debug|x64
+               {B88C58EB-B144-403B-85F7-7A5B45E643E3}.Release|x64.ActiveCfg = 
Release|x64
+               {B88C58EB-B144-403B-85F7-7A5B45E643E3}.Release|x64.Build.0 = 
Release|x64
+               {94D2CD59-A5F3-4504-BF01-0A3B95CE12B5}.Debug|x64.ActiveCfg = 
Debug|x64
+               {94D2CD59-A5F3-4504-BF01-0A3B95CE12B5}.Debug|x64.Build.0 = 
Debug|x64
+               {94D2CD59-A5F3-4504-BF01-0A3B95CE12B5}.Release|x64.ActiveCfg = 
Release|x64
+               {94D2CD59-A5F3-4504-BF01-0A3B95CE12B5}.Release|x64.Build.0 = 
Release|x64
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
        EndGlobalSection
+       GlobalSection(NestedProjects) = preSolution
+               {B88C58EB-B144-403B-85F7-7A5B45E643E3} = 
{520C96EC-F929-4365-8D78-CC5785419B62}
+               {94D2CD59-A5F3-4504-BF01-0A3B95CE12B5} = 
{520C96EC-F929-4365-8D78-CC5785419B62}
+       EndGlobalSection
        GlobalSection(ExtensibilityGlobals) = postSolution
                SolutionGuid = {B30A49F0-1C96-4D6C-A222-0088B1D7FBBE}
        EndGlobalSection

Reply via email to