You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sb...@apache.org on 2016/09/20 08:44:52 UTC
[10/13] ignite git commit: IGNITE-3199 .NET: Add ASP.NET
Session-State Store Provider
http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteOutputCacheProviderTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteOutputCacheProviderTest.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteOutputCacheProviderTest.cs
new file mode 100644
index 0000000..bf2fd7e
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteOutputCacheProviderTest.cs
@@ -0,0 +1,172 @@
+\ufeff/*
+ * 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.
+ */
+
+namespace Apache.Ignite.AspNet.Tests
+{
+ using System;
+ using System.Collections.Specialized;
+ using System.Threading;
+ using Apache.Ignite.Core;
+ using Apache.Ignite.Core.Common;
+ using Apache.Ignite.Core.Tests;
+ using NUnit.Framework;
+
+ /// <summary>
+ /// Tests for <see cref="IgniteOutputCacheProvider"/>
+ /// </summary>
+ public class IgniteOutputCacheProviderTest
+ {
+ /** Grid name XML config attribute. */
+ private const string GridNameAttr = "gridName";
+
+ /** Cache name XML config attribute. */
+ private const string CacheNameAttr = "cacheName";
+
+ /** Cache name XML config attribute. */
+ private const string SectionNameAttr = "igniteConfigurationSectionName";
+
+ /** Grid name. */
+ private const string GridName = "grid1";
+
+ /** Cache name. */
+ private const string CacheName = "myCache";
+
+ /// <summary>
+ /// Fixture setup.
+ /// </summary>
+ [TestFixtureSetUp]
+ public void TestFixtureSetUp()
+ {
+ Ignition.Start(new IgniteConfiguration(TestUtils.GetTestConfiguration()) {GridName = GridName});
+ }
+
+ /// <summary>
+ /// Fixture teardown.
+ /// </summary>
+ [TestFixtureTearDown]
+ public void TestFixtureTearDown()
+ {
+ Ignition.StopAll(true);
+ }
+
+ /// <summary>
+ /// Tests provider initialization.
+ /// </summary>
+ [Test]
+ public void TestInitialization()
+ {
+ var cacheProvider = new IgniteOutputCacheProvider();
+
+ // Not initialized.
+ Assert.Throws<InvalidOperationException>(() => cacheProvider.Get("1"));
+
+ // Invalid section.
+ Assert.Throws<IgniteException>(() =>
+ cacheProvider.Initialize("testName", new NameValueCollection
+ {
+ {SectionNameAttr, "invalidSection"},
+ }));
+
+ // Valid grid.
+ cacheProvider = GetProvider();
+
+ cacheProvider.Set("1", 1, DateTime.MaxValue);
+ Assert.AreEqual(1, cacheProvider.Get("1"));
+ }
+
+ /// <summary>
+ /// Tests autostart from web configuration section.
+ /// </summary>
+ [Test]
+ public void TestStartFromWebConfigSection()
+ {
+ var cacheProvider = new IgniteOutputCacheProvider();
+
+ cacheProvider.Initialize("testName2", new NameValueCollection
+ {
+ {SectionNameAttr, "igniteConfiguration2"},
+ {CacheNameAttr, "cacheName2"}
+ });
+
+ cacheProvider.Set("1", 3, DateTime.MaxValue);
+ Assert.AreEqual(3, cacheProvider.Get("1"));
+ }
+
+ /// <summary>
+ /// Tests provider caching.
+ /// </summary>
+ [Test]
+ public void TestCaching()
+ {
+ var cacheProvider = GetProvider();
+
+ Assert.AreEqual(null, cacheProvider.Get("1"));
+ cacheProvider.Set("1", 1, DateTime.MaxValue);
+ Assert.AreEqual(1, cacheProvider.Get("1"));
+
+ cacheProvider.Remove("1");
+ Assert.AreEqual(null, cacheProvider.Get("1"));
+
+ Assert.AreEqual(null, cacheProvider.Add("2", 2, DateTime.MaxValue));
+ Assert.AreEqual(2, cacheProvider.Add("2", 5, DateTime.MaxValue));
+ }
+
+ /// <summary>
+ /// Tests cache expiration.
+ /// </summary>
+ [Test]
+ public void TestExpiry()
+ {
+ var cacheProvider = GetProvider();
+ cacheProvider.Remove("1");
+
+ // Set
+ cacheProvider.Set("1", 1, DateTime.UtcNow.AddSeconds(1.3));
+ Assert.AreEqual(1, cacheProvider.Get("1"));
+ Thread.Sleep(2000);
+ Assert.AreEqual(null, cacheProvider.Get("1"));
+
+ cacheProvider.Set("1", 1, DateTime.UtcNow);
+ Assert.AreEqual(null, cacheProvider.Get("1"));
+
+ // Add
+ cacheProvider.Add("1", 1, DateTime.UtcNow.AddSeconds(0.7));
+ Assert.AreEqual(1, cacheProvider.Get("1"));
+ Thread.Sleep(2000);
+ Assert.AreEqual(null, cacheProvider.Get("1"));
+
+ cacheProvider.Add("1", 1, DateTime.UtcNow);
+ Assert.AreEqual(null, cacheProvider.Get("1"));
+ }
+
+ /// <summary>
+ /// Gets the initialized provider.
+ /// </summary>
+ private static IgniteOutputCacheProvider GetProvider()
+ {
+ var cacheProvider = new IgniteOutputCacheProvider();
+
+ cacheProvider.Initialize("testName", new NameValueCollection
+ {
+ {GridNameAttr, GridName},
+ {CacheNameAttr, CacheName}
+ });
+
+ return cacheProvider;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateItemCollectionTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateItemCollectionTest.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateItemCollectionTest.cs
new file mode 100644
index 0000000..137382e
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateItemCollectionTest.cs
@@ -0,0 +1,267 @@
+\ufeff/*
+ * 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.
+ */
+
+namespace Apache.Ignite.AspNet.Tests
+{
+ using System;
+ using System.IO;
+ using System.Linq;
+ using Apache.Ignite.AspNet.Impl;
+ using Apache.Ignite.Core.Impl.Binary;
+ using Apache.Ignite.Core.Impl.Binary.IO;
+ using NUnit.Framework;
+
+ /// <summary>
+ /// Tests for <see cref="IgniteSessionStateItemCollection"/>.
+ /// </summary>
+ public class IgniteSessionStateItemCollectionTest
+ {
+ /// <summary>
+ /// Tests the empty collection.
+ /// </summary>
+ [Test]
+ public void TestEmpty()
+ {
+ var col1 = new IgniteSessionStateItemCollection();
+ var col2 = SerializeDeserialize(col1);
+
+ foreach (var col in new[] { col1, col2 })
+ {
+ Assert.IsFalse(col.Dirty);
+ Assert.IsFalse(col.IsSynchronized);
+ Assert.AreEqual(0, col.Count);
+ Assert.IsNotNull(col.SyncRoot);
+ Assert.IsEmpty(col);
+ Assert.IsEmpty(col.OfType<string>().ToArray());
+ Assert.IsEmpty(col.Keys);
+ Assert.IsNotNull(col.SyncRoot);
+
+ Assert.IsNull(col["key"]);
+ Assert.Throws<ArgumentOutOfRangeException>(() => col[0] = "x");
+ Assert.Throws<ArgumentOutOfRangeException>(() => Assert.AreEqual(0, col[0]));
+ Assert.Throws<ArgumentOutOfRangeException>(() => col.RemoveAt(0));
+
+ col.Clear();
+ col.Remove("test");
+
+ Assert.AreEqual(0, col.Count);
+
+ col.Dirty = true;
+ Assert.IsTrue(col.Dirty);
+ }
+ }
+
+ /// <summary>
+ /// Tests the modification.
+ /// </summary>
+ [Test]
+ public void TestModification()
+ {
+ var col = new IgniteSessionStateItemCollection();
+
+ // Populate and check.
+ col["key"] = "val";
+ col["1"] = 1;
+
+ Assert.AreEqual("val", col["key"]);
+ Assert.AreEqual(1, col["1"]);
+
+ Assert.AreEqual(2, col.Count);
+ Assert.IsTrue(col.Dirty);
+
+ CollectionAssert.AreEquivalent(new[] {"key", "1"}, col);
+ CollectionAssert.AreEquivalent(new[] {"key", "1"}, col.Keys);
+
+ // Modify using index.
+ col[0] = "val1";
+ col[1] = 2;
+
+ Assert.AreEqual("val1", col["key"]);
+ Assert.AreEqual(2, col["1"]);
+
+ // Modify using key.
+ col["1"] = 3;
+ col["key"] = "val2";
+
+ Assert.AreEqual("val2", col["key"]);
+ Assert.AreEqual(3, col["1"]);
+
+ // CopyTo.
+ var keys = new string[5];
+ col.CopyTo(keys, 2);
+ Assert.AreEqual(new[] {null, null, "key", "1", null}, keys);
+
+ // Remove.
+ col["2"] = 2;
+ col["3"] = 3;
+
+ col.Remove("invalid");
+ Assert.AreEqual(4, col.Count);
+
+ col.Remove("1");
+
+ Assert.AreEqual(new[] { "key", "2", "3" }, col.OfType<string>());
+ Assert.AreEqual(null, col["1"]);
+
+ Assert.AreEqual("val2", col["key"]);
+ Assert.AreEqual("val2", col[0]);
+
+ Assert.AreEqual(2, col["2"]);
+ Assert.AreEqual(2, col[1]);
+
+ Assert.AreEqual(3, col["3"]);
+ Assert.AreEqual(3, col[2]);
+
+ // RemoveAt.
+ col.RemoveAt(0);
+ Assert.AreEqual(new[] { "2", "3" }, col.OfType<string>());
+
+ // Clear.
+ Assert.AreEqual(2, col.Count);
+
+ col.Clear();
+ Assert.AreEqual(0, col.Count);
+
+ // Set dirty.
+ var col1 = new IgniteSessionStateItemCollection {Dirty = true};
+ Assert.IsTrue(col1.Dirty);
+ }
+
+ /// <summary>
+ /// Tests dirty tracking.
+ /// </summary>
+ [Test]
+ public void TestApplyChanges()
+ {
+ Func<IgniteSessionStateItemCollection> getCol = () =>
+ {
+ var res = new IgniteSessionStateItemCollection();
+
+ res["1"] = 1;
+ res["2"] = 2;
+ res["3"] = 3;
+
+ return res;
+ };
+
+ var col = getCol();
+
+ var col0 = SerializeDeserialize(col);
+
+ Assert.AreEqual(3, col0.Count);
+
+ col0.Remove("1");
+ col0["2"] = 22;
+ col0["4"] = 44;
+
+ // Apply non-serialized changes.
+ col.ApplyChanges(col0);
+
+ Assert.AreEqual(3, col.Count);
+ Assert.AreEqual(null, col["1"]);
+ Assert.AreEqual(22, col["2"]);
+ Assert.AreEqual(3, col["3"]);
+ Assert.AreEqual(44, col["4"]);
+
+ // Apply serialized changes without WriteChangesOnly.
+ col = getCol();
+ col.ApplyChanges(SerializeDeserialize(col0));
+
+ Assert.AreEqual(3, col.Count);
+ Assert.AreEqual(null, col["1"]);
+ Assert.AreEqual(22, col["2"]);
+ Assert.AreEqual(3, col["3"]);
+ Assert.AreEqual(44, col["4"]);
+
+ // Apply serialized changes with WriteChangesOnly.
+ col = getCol();
+ col.ApplyChanges(SerializeDeserialize(col0, true));
+
+ Assert.AreEqual(3, col.Count);
+ Assert.AreEqual(null, col["1"]);
+ Assert.AreEqual(22, col["2"]);
+ Assert.AreEqual(3, col["3"]);
+ Assert.AreEqual(44, col["4"]);
+
+ // Remove key then add back.
+ col0.Remove("2");
+ col0.Remove("3");
+ col0["2"] = 222;
+
+ col = getCol();
+ col.ApplyChanges(SerializeDeserialize(col0));
+
+ Assert.AreEqual(2, col.Count);
+ Assert.AreEqual(222, col["2"]);
+ Assert.AreEqual(44, col["4"]);
+
+ // Remove all.
+ col0 = SerializeDeserialize(getCol());
+ col0.Clear();
+
+ col = getCol();
+ col.ApplyChanges(SerializeDeserialize(col0, true));
+
+ Assert.AreEqual(0, col.Count);
+
+ // Add to empty.
+ col0["-1"] = -1;
+ col0["-2"] = -2;
+
+ col = getCol();
+ col.ApplyChanges(SerializeDeserialize(col0));
+
+ Assert.AreEqual(2, col.Count);
+ Assert.AreEqual(-1, col0["-1"]);
+ Assert.AreEqual(-2, col0["-2"]);
+
+ // Remove initial key, then add it back, then remove again.
+ col0 = SerializeDeserialize(getCol());
+
+ col0.Remove("1");
+ col0.Remove("2");
+ col0["1"] = "111";
+ col0.Remove("1");
+
+ col = getCol();
+ col.ApplyChanges(SerializeDeserialize(col0, true));
+
+ Assert.AreEqual(1, col.Count);
+ Assert.AreEqual(3, col["3"]);
+ }
+
+ /// <summary>
+ /// Serializes and deserializes back an instance.
+ /// </summary>
+ private static IgniteSessionStateItemCollection SerializeDeserialize(IgniteSessionStateItemCollection data,
+ bool changesOnly = false)
+ {
+ var marsh = BinaryUtils.Marshaller;
+
+ using (var stream = new BinaryHeapStream(128))
+ {
+ var writer = marsh.StartMarshal(stream);
+
+ data.WriteBinary(writer.GetRawWriter(), changesOnly);
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ return new IgniteSessionStateItemCollection(marsh.StartUnmarshal(stream));
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreDataTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreDataTest.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreDataTest.cs
new file mode 100644
index 0000000..e8dcd7c
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreDataTest.cs
@@ -0,0 +1,117 @@
+\ufeff/*
+ * 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.
+ */
+
+namespace Apache.Ignite.AspNet.Tests
+{
+ using System;
+ using System.IO;
+ using System.Reflection;
+ using System.Web;
+ using Apache.Ignite.AspNet.Impl;
+ using Apache.Ignite.Core.Impl.Binary;
+ using Apache.Ignite.Core.Impl.Binary.IO;
+ using NUnit.Framework;
+
+ /// <summary>
+ /// Tests for <see cref="IgniteSessionStateStoreData"/>.
+ /// </summary>
+ public class IgniteSessionStateStoreDataTest
+ {
+ /// <summary>
+ /// Tests the data.
+ /// </summary>
+ [Test]
+ public void TestData()
+ {
+ // Modification method is internal.
+ var statics = new HttpStaticObjectsCollection();
+ statics.GetType().GetMethod("Add", BindingFlags.Instance | BindingFlags.NonPublic)
+ .Invoke(statics, new object[] { "int", typeof(int), false });
+
+ var data = new IgniteSessionStateStoreData(statics, 44);
+
+ data.Items["key"] = "val";
+
+ Assert.AreEqual(44, data.Timeout);
+ Assert.AreEqual(1, data.StaticObjects.Count);
+ Assert.AreEqual(0, data.StaticObjects["int"]);
+ Assert.AreEqual("val", data.Items["key"]);
+ }
+
+ /// <summary>
+ /// Tests the empty data.
+ /// </summary>
+ [Test]
+ public void TestEmpty()
+ {
+ var data = new IgniteSessionStateStoreData(null, 0);
+
+ Assert.AreEqual(0, data.LockId);
+ Assert.AreEqual(0, data.Items.Count);
+ Assert.AreEqual(0, data.Timeout);
+ Assert.IsNull(data.LockNodeId);
+ Assert.IsNull(data.LockTime);
+ Assert.IsNull(data.StaticObjects);
+ }
+
+ /// <summary>
+ /// Tests the serialization.
+ /// </summary>
+ [Test]
+ public void TestSerialization()
+ {
+ var data = new IgniteSessionStateStoreData(null, 96)
+ {
+ Timeout = 97,
+ LockId = 11,
+ LockNodeId = Guid.NewGuid(),
+ LockTime = DateTime.UtcNow.AddHours(-1),
+ };
+
+ data.Items["key1"] = 1;
+ data.Items["key2"] = 2;
+
+ var data0 = SerializeDeserialize(data);
+
+ Assert.AreEqual(data.Timeout, data0.Timeout);
+ Assert.AreEqual(data.LockId, data0.LockId);
+ Assert.AreEqual(data.LockNodeId, data0.LockNodeId);
+ Assert.AreEqual(data.LockTime, data0.LockTime);
+ Assert.AreEqual(data.Items.Keys, data0.Items.Keys);
+ }
+
+
+ /// <summary>
+ /// Serializes and deserializes back an instance.
+ /// </summary>
+ private static IgniteSessionStateStoreData SerializeDeserialize(IgniteSessionStateStoreData data)
+ {
+ var marsh = BinaryUtils.Marshaller;
+
+ using (var stream = new BinaryHeapStream(128))
+ {
+ var writer = marsh.StartMarshal(stream);
+
+ data.WriteBinary(writer.GetRawWriter(), false);
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ return new IgniteSessionStateStoreData(marsh.StartUnmarshal(stream));
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreProviderTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreProviderTest.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreProviderTest.cs
new file mode 100644
index 0000000..fc239ad
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/IgniteSessionStateStoreProviderTest.cs
@@ -0,0 +1,425 @@
+\ufeff/*
+ * 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.
+ */
+
+namespace Apache.Ignite.AspNet.Tests
+{
+ using System;
+ using System.Collections.Specialized;
+ using System.Linq;
+ using System.Reflection;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using System.Web;
+ using System.Web.SessionState;
+ using Apache.Ignite.Core;
+ using Apache.Ignite.Core.Common;
+ using Apache.Ignite.Core.Tests;
+ using NUnit.Framework;
+
+ /// <summary>
+ /// Tests for <see cref="IgniteSessionStateStoreProvider"/>.
+ /// </summary>
+ public class IgniteSessionStateStoreProviderTest
+ {
+ /** Grid name XML config attribute. */
+ private const string GridNameAttr = "gridName";
+
+ /** Cache name XML config attribute. */
+ private const string CacheNameAttr = "cacheName";
+
+ /** Section name XML config attribute. */
+ private const string SectionNameAttr = "igniteConfigurationSectionName";
+
+ /** Grid name. */
+ private const string GridName = "grid1";
+
+ /** Cache name. */
+ private const string CacheName = "myCache";
+
+ /** Session id. */
+ private const string Id = "1";
+
+ /** Test context. */
+ private static readonly HttpContext HttpContext =
+ new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
+
+ /// <summary>
+ /// Fixture setup.
+ /// </summary>
+ [TestFixtureSetUp]
+ public void TestFixtureSetUp()
+ {
+ Ignition.Start(new IgniteConfiguration(TestUtils.GetTestConfiguration()) { GridName = GridName });
+ }
+
+ /// <summary>
+ /// Fixture teardown.
+ /// </summary>
+ [TestFixtureTearDown]
+ public void TestFixtureTearDown()
+ {
+ Ignition.StopAll(true);
+ }
+
+ /// <summary>
+ /// Test teardown.
+ /// </summary>
+ [TearDown]
+ public void TearDown()
+ {
+ // Clear all caches.
+ var ignite = Ignition.GetIgnite(GridName);
+ ignite.GetCacheNames().ToList().ForEach(x => ignite.GetCache<object, object>(x).RemoveAll());
+ }
+
+ /// <summary>
+ /// Test setup.
+ /// </summary>
+ [SetUp]
+ public void SetUp()
+ {
+ // Make sure caches are empty.
+ var ignite = Ignition.GetIgnite(GridName);
+
+ foreach (var cache in ignite.GetCacheNames().Select(x => ignite.GetCache<object, object>(x)))
+ CollectionAssert.IsEmpty(cache.ToArray());
+ }
+
+ /// <summary>
+ /// Tests provider initialization.
+ /// </summary>
+ [Test]
+ public void TestInitialization()
+ {
+ var stateProvider = new IgniteSessionStateStoreProvider();
+
+ SessionStateActions actions;
+ bool locked;
+ TimeSpan lockAge;
+ object lockId;
+
+ // Not initialized.
+ Assert.Throws<InvalidOperationException>(() =>
+ stateProvider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions));
+
+ // Invalid section.
+ Assert.Throws<IgniteException>(() =>
+ stateProvider.Initialize("testName", new NameValueCollection
+ {
+ {SectionNameAttr, "invalidSection"},
+ {CacheNameAttr, CacheName}
+ }));
+
+ // Valid grid.
+ stateProvider = GetProvider();
+
+ CheckProvider(stateProvider);
+
+ // Same grid once again.
+ stateProvider = GetProvider();
+
+ CheckProvider(stateProvider);
+ }
+
+ /// <summary>
+ /// Tests autostart from web configuration section.
+ /// </summary>
+ [Test]
+ public void TestStartFromWebConfigSection()
+ {
+ var provider = new IgniteSessionStateStoreProvider();
+
+ provider.Initialize("testName3", new NameValueCollection
+ {
+ {SectionNameAttr, "igniteConfiguration3"},
+ {CacheNameAttr, "cacheName3"}
+ });
+
+ CheckProvider(provider);
+ }
+
+ /// <summary>
+ /// Tests the caching.
+ /// </summary>
+ [Test]
+ public void TestCaching()
+ {
+ bool locked;
+ TimeSpan lockAge;
+ object lockId;
+ SessionStateActions actions;
+
+ var provider = GetProvider();
+
+ // Not locked, no item.
+ var res = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions);
+ Assert.IsNull(res);
+ Assert.IsNull(lockId);
+ Assert.IsFalse(locked);
+ Assert.AreEqual(TimeSpan.Zero, lockAge);
+ Assert.AreEqual(SessionStateActions.None, actions);
+
+ // Exclusive: not locked, no item.
+ res = provider.GetItemExclusive(HttpContext, Id, out locked, out lockAge, out lockId, out actions);
+ Assert.IsNull(res);
+ Assert.IsNull(lockId);
+ Assert.IsFalse(locked);
+ Assert.AreEqual(TimeSpan.Zero, lockAge);
+ Assert.AreEqual(SessionStateActions.None, actions);
+
+ // Add item.
+ provider.CreateUninitializedItem(HttpContext, Id, 7);
+
+ // Check added item.
+ res = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions);
+ Assert.IsNotNull(res);
+ Assert.IsNull(lockId);
+ Assert.AreEqual(7, res.Timeout);
+ Assert.IsFalse(locked);
+ Assert.AreEqual(TimeSpan.Zero, lockAge);
+ Assert.AreEqual(SessionStateActions.None, actions);
+
+ // Lock and update.
+ res = provider.GetItemExclusive(HttpContext, Id, out locked, out lockAge, out lockId, out actions);
+ Assert.IsNotNull(res);
+ Assert.IsNotNull(lockId);
+ Assert.IsFalse(locked);
+ Assert.AreEqual(TimeSpan.Zero, lockAge);
+ Assert.AreEqual(SessionStateActions.None, actions);
+ provider.SetAndReleaseItemExclusive(HttpContext, Id, UpdateStoreData(res), lockId, true);
+
+ // Not locked, item present.
+ res = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions);
+ CheckStoreData(res);
+ Assert.IsNull(lockId);
+ Assert.IsFalse(locked);
+ Assert.AreEqual(TimeSpan.Zero, lockAge);
+ Assert.AreEqual(SessionStateActions.None, actions);
+
+ // Lock item.
+ res = provider.GetItemExclusive(HttpContext, Id, out locked, out lockAge, out lockId, out actions);
+ CheckStoreData(res);
+ Assert.IsNotNull(lockId);
+ Assert.IsFalse(locked);
+ Assert.AreEqual(TimeSpan.Zero, lockAge);
+ Assert.AreEqual(SessionStateActions.None, actions);
+
+ // Try to get it in a different thread.
+ Task.Factory.StartNew(() =>
+ {
+ object lockId1; // do not overwrite lockId
+ res = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId1, out actions);
+ Assert.IsNull(res);
+ Assert.IsNotNull(lockId1);
+ Assert.IsTrue(locked);
+ Assert.Greater(lockAge, TimeSpan.Zero);
+ Assert.AreEqual(SessionStateActions.None, actions);
+ }).Wait();
+
+ // Try to get it in a different thread.
+ Task.Factory.StartNew(() =>
+ {
+ object lockId1; // do not overwrite lockId
+ res = provider.GetItemExclusive(HttpContext, Id, out locked, out lockAge, out lockId1, out actions);
+ Assert.IsNull(res);
+ Assert.IsNotNull(lockId1);
+ Assert.IsTrue(locked);
+ Assert.Greater(lockAge, TimeSpan.Zero);
+ Assert.AreEqual(SessionStateActions.None, actions);
+ }).Wait();
+
+ // Release item.
+ provider.ReleaseItemExclusive(HttpContext, Id, lockId);
+
+ // Make sure it is accessible in a different thread.
+ Task.Factory.StartNew(() =>
+ {
+ res = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions);
+ Assert.IsNotNull(res);
+ Assert.IsFalse(locked);
+ Assert.AreEqual(TimeSpan.Zero, lockAge);
+ Assert.AreEqual(SessionStateActions.None, actions);
+ }).Wait();
+
+ // Remove item.
+ provider.RemoveItem(HttpContext, Id, lockId, null);
+
+ // Check removal.
+ res = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions);
+ Assert.IsNull(res);
+ Assert.IsFalse(locked);
+ Assert.AreEqual(TimeSpan.Zero, lockAge);
+ Assert.AreEqual(SessionStateActions.None, actions);
+ }
+
+ /// <summary>
+ /// Tests the create new store data.
+ /// </summary>
+ [Test]
+ public void TestCreateNewStoreData()
+ {
+ var provider = GetProvider();
+
+ var data = provider.CreateNewStoreData(HttpContext, 56);
+
+ Assert.AreEqual(56, data.Timeout);
+ Assert.IsEmpty(data.Items);
+ Assert.IsEmpty(data.StaticObjects);
+
+ // Check that caches are empty.
+ var ignite = Ignition.GetIgnite(GridName);
+ Assert.IsFalse(ignite.GetCacheNames().SelectMany(x => ignite.GetCache<int, int>(x)).Any());
+ }
+
+ /// <summary>
+ /// Tests the expiry.
+ /// </summary>
+ [Test]
+ [Category(TestUtils.CategoryIntensive)] // Minimum expiration is 1 minute
+ public void TestExpiry()
+ {
+ var provider = GetProvider();
+
+ bool locked;
+ TimeSpan lockAge;
+ object lockId;
+ SessionStateActions actions;
+
+ // Callbacks are not supported for now.
+ Assert.IsFalse(GetProvider().SetItemExpireCallback(null));
+
+ // Check there is no item.
+ var res = provider.GetItem(HttpContext, "myId", out locked, out lockAge, out lockId, out actions);
+ Assert.IsNull(res);
+
+ // Put an item.
+ provider.CreateUninitializedItem(HttpContext, "myId", 1);
+
+ // Check that it is there.
+ res = provider.GetItem(HttpContext, "myId", out locked, out lockAge, out lockId, out actions);
+ Assert.IsNotNull(res);
+
+ // Wait a minute and check again.
+ Thread.Sleep(TimeSpan.FromMinutes(1.05));
+
+ res = provider.GetItem(HttpContext, "myId", out locked, out lockAge, out lockId, out actions);
+ Assert.IsNull(res);
+ }
+
+ /// <summary>
+ /// Tests the create uninitialized item.
+ /// </summary>
+ [Test]
+ public void TestCreateUninitializedItem()
+ {
+ bool locked;
+ TimeSpan lockAge;
+ object lockId;
+ SessionStateActions actions;
+
+ var provider = GetProvider();
+ provider.CreateUninitializedItem(HttpContext, "myId", 45);
+
+ var res = provider.GetItem(HttpContext, "myId", out locked, out lockAge, out lockId, out actions);
+ Assert.IsNotNull(res);
+ Assert.AreEqual(45, res.Timeout);
+ Assert.AreEqual(0, res.Items.Count);
+ Assert.AreEqual(0, res.StaticObjects.Count);
+ }
+
+ /// <summary>
+ /// Updates the store data.
+ /// </summary>
+ private static SessionStateStoreData UpdateStoreData(SessionStateStoreData data)
+ {
+ data.Timeout = 8;
+
+ data.Items["name1"] = 1;
+ data.Items["name2"] = "2";
+
+ var statics = data.StaticObjects;
+
+ // Modification method is internal.
+ statics.GetType().GetMethod("Add", BindingFlags.Instance | BindingFlags.NonPublic)
+ .Invoke(statics, new object[] {"int", typeof(int), false});
+
+ CheckStoreData(data);
+
+ return data;
+ }
+
+ /// <summary>
+ /// Checks that store data is the same as <see cref="UpdateStoreData"/> returns.
+ /// </summary>
+ private static void CheckStoreData(SessionStateStoreData data)
+ {
+ Assert.IsNotNull(data);
+
+ Assert.AreEqual(8, data.Timeout);
+
+ Assert.AreEqual(1, data.Items["name1"]);
+ Assert.AreEqual(1, data.Items[0]);
+
+ Assert.AreEqual("2", data.Items["name2"]);
+ Assert.AreEqual("2", data.Items[1]);
+
+ Assert.AreEqual(0, data.StaticObjects["int"]);
+ }
+
+ /// <summary>
+ /// Gets the initialized provider.
+ /// </summary>
+ private static IgniteSessionStateStoreProvider GetProvider()
+ {
+ var stateProvider = new IgniteSessionStateStoreProvider();
+
+ stateProvider.Initialize("testName", new NameValueCollection
+ {
+ {GridNameAttr, GridName},
+ {CacheNameAttr, CacheName}
+ });
+
+ return stateProvider;
+ }
+
+ /// <summary>
+ /// Checks the provider.
+ /// </summary>
+ private static void CheckProvider(SessionStateStoreProviderBase provider)
+ {
+ bool locked;
+ TimeSpan lockAge;
+ object lockId;
+ SessionStateActions actions;
+
+ provider.InitializeRequest(HttpContext);
+ provider.CreateUninitializedItem(HttpContext, Id, 42);
+
+ var data = provider.GetItem(HttpContext, Id, out locked, out lockAge, out lockId, out actions);
+ Assert.IsNotNull(data);
+ Assert.AreEqual(42, data.Timeout);
+ Assert.IsFalse(locked);
+ Assert.AreEqual(TimeSpan.Zero, lockAge);
+ Assert.IsNull(lockId);
+ Assert.AreEqual(SessionStateActions.None, actions);
+
+ provider.ResetItemTimeout(HttpContext, Id);
+ provider.EndRequest(HttpContext);
+ provider.Dispose();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Properties/AssemblyInfo.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Properties/AssemblyInfo.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..afaa6f0
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,42 @@
+\ufeff/*
+* 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.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Apache.Ignite.AspNet.Tests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Apache Software Foundation")]
+[assembly: AssemblyProduct("Apache Ignite.NET")]
+[assembly: AssemblyCopyright("Copyright � 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+
+[assembly: Guid("18ea4c71-a11d-4ab1-8042-418f7559d84f")]
+
+[assembly: AssemblyVersion("1.8.0.14218")]
+[assembly: AssemblyFileVersion("1.8.0.14218")]
+[assembly: AssemblyInformationalVersion("1.8.0")]
+
+[assembly: CLSCompliant(true)]
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/packages.config
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/packages.config b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/packages.config
new file mode 100644
index 0000000..c1198cb
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/packages.config
@@ -0,0 +1,22 @@
+\ufeff<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ 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.
+-->
+
+<packages>
+ <package id="NUnit.Runners" version="2.6.3" targetFramework="net40" />
+</packages>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj b/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj
index 0c273e0..1ac452f 100644
--- a/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.csproj
@@ -47,7 +47,13 @@
</ItemGroup>
<ItemGroup>
<Compile Include="IgniteOutputCacheProvider.cs" />
+ <Compile Include="IgniteSessionStateStoreProvider.cs" />
<Compile Include="IgniteWebUtils.cs" />
+ <Compile Include="Impl\ConfigUtil.cs" />
+ <Compile Include="Impl\ExpiryCacheHolder.cs" />
+ <Compile Include="Impl\IgniteSessionStateStoreData.cs" />
+ <Compile Include="Impl\IgniteSessionStateItemCollection.cs" />
+ <Compile Include="Impl\SessionStateLockResult.cs" />
<Compile Include="Package-Info.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.ruleset
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.ruleset b/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.ruleset
index bc7683a..5a77e40 100644
--- a/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.ruleset
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Apache.Ignite.AspNet.ruleset
@@ -2,7 +2,10 @@
<RuleSet Name="Rules for Apache.Ignite.AspNet" Description="Code analysis rules for Apache.Ignite.AspNet.csproj." ToolsVersion="14.0">
<IncludeAll Action="Error" />
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
+ <Rule Id="CA1020" Action="None" />
+ <Rule Id="CA1303" Action="None" />
<Rule Id="CA1704" Action="None" />
+ <Rule Id="CA2202" Action="None" />
<Rule Id="CA2204" Action="None" />
<Rule Id="CA2243" Action="None" />
</Rules>
http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteOutputCacheProvider.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteOutputCacheProvider.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteOutputCacheProvider.cs
index 64216dd..d232726 100644
--- a/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteOutputCacheProvider.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteOutputCacheProvider.cs
@@ -18,23 +18,20 @@
namespace Apache.Ignite.AspNet
{
using System;
- using System.Collections.Generic;
using System.Collections.Specialized;
- using System.Configuration;
using System.Diagnostics.CodeAnalysis;
- using System.Globalization;
using System.Web.Caching;
+ using Apache.Ignite.AspNet.Impl;
using Apache.Ignite.Core;
using Apache.Ignite.Core.Cache;
- using Apache.Ignite.Core.Cache.Expiry;
- using Apache.Ignite.Core.Common;
/// <summary>
/// ASP.NET output cache provider that uses Ignite cache as a storage.
/// <para />
/// You can either start Ignite yourself, and provide <c>gridName</c> attribute,
/// or provide <c>igniteConfigurationSectionName</c> attribute to start Ignite automatically from specified
- /// configuration section (see <see cref="IgniteConfigurationSection"/>).
+ /// configuration section (see <see cref="IgniteConfigurationSection"/>)
+ /// using <c>igniteConfigurationSectionName</c>.
/// <para />
/// <c>cacheName</c> attribute specifies Ignite cache name to use for data storage. This attribute can be omitted
/// if cache name is null.
@@ -42,26 +39,7 @@ namespace Apache.Ignite.AspNet
public class IgniteOutputCacheProvider : OutputCacheProvider
{
/** */
- private const string GridName = "gridName";
-
- /** */
- private const string CacheName = "cacheName";
-
- /** */
- private const string IgniteConfigurationSectionName = "igniteConfigurationSectionName";
-
- /** Max number of cached expiry caches. */
- private const int MaxExpiryCaches = 1000;
-
- /** */
- private volatile ICache<string, object> _cache;
-
- /** Cached caches per expiry seconds. */
- private volatile Dictionary<long, ICache<string, object>> _expiryCaches =
- new Dictionary<long, ICache<string, object>>();
-
- /** Sync object. */
- private readonly object _syncRoot = new object();
+ private volatile ExpiryCacheHolder<string, object> _expiryCacheHolder;
/// <summary>
/// Returns a reference to the specified entry in the output cache.
@@ -88,7 +66,7 @@ namespace Apache.Ignite.AspNet
/// </returns>
public override object Add(string key, object entry, DateTime utcExpiry)
{
- return GetCacheWithExpiry(utcExpiry).GetAndPutIfAbsent(key, entry).Value;
+ return _expiryCacheHolder.GetCacheWithExpiry(utcExpiry).GetAndPutIfAbsent(key, entry).Value;
}
/// <summary>
@@ -99,7 +77,7 @@ namespace Apache.Ignite.AspNet
/// <param name="utcExpiry">The time and date on which the cached <paramref name="entry" /> expires.</param>
public override void Set(string key, object entry, DateTime utcExpiry)
{
- GetCacheWithExpiry(utcExpiry)[key] = entry;
+ _expiryCacheHolder.GetCacheWithExpiry(utcExpiry)[key] = entry;
}
/// <summary>
@@ -121,46 +99,11 @@ namespace Apache.Ignite.AspNet
{
base.Initialize(name, config);
- var gridName = config[GridName];
- var cacheName = config[CacheName];
- var cfgSection = config[IgniteConfigurationSectionName];
-
- try
- {
- var grid = cfgSection != null
- ? StartFromApplicationConfiguration(cfgSection)
- : Ignition.GetIgnite(gridName);
+ var cache = ConfigUtil.InitializeCache<string, object>(config, GetType());
- _cache = grid.GetOrCreateCache<string, object>(cacheName);
- }
- catch (Exception ex)
- {
- throw new IgniteException(string.Format(CultureInfo.InvariantCulture,
- "Failed to initialize {0}: {1}", GetType(), ex), ex);
- }
+ _expiryCacheHolder = new ExpiryCacheHolder<string, object>(cache);
}
- /// <summary>
- /// Starts Ignite from application configuration.
- /// </summary>
- private static IIgnite StartFromApplicationConfiguration(string sectionName)
- {
- var section = ConfigurationManager.GetSection(sectionName) as IgniteConfigurationSection;
-
- if (section == null)
- throw new ConfigurationErrorsException(string.Format(CultureInfo.InvariantCulture,
- "Could not find {0} with name '{1}'", typeof(IgniteConfigurationSection).Name, sectionName));
-
- var config = section.IgniteConfiguration;
-
- if (string.IsNullOrWhiteSpace(config.IgniteHome))
- {
- // IgniteHome not set by user: populate from default directory
- config = new IgniteConfiguration(config) {IgniteHome = IgniteWebUtils.GetWebIgniteHome()};
- }
-
- return Ignition.Start(config);
- }
/// <summary>
/// Gets the cache.
@@ -169,51 +112,12 @@ namespace Apache.Ignite.AspNet
{
get
{
- var cache = _cache;
-
- if (cache == null)
- throw new InvalidOperationException("IgniteOutputCacheProvider has not been initialized.");
-
- return cache;
- }
- }
-
- /// <summary>
- /// Gets the cache with expiry policy according to provided expiration date.
- /// </summary>
- /// <param name="utcExpiry">The UTC expiry.</param>
- /// <returns>Cache with expiry policy.</returns>
- private ICache<string, object> GetCacheWithExpiry(DateTime utcExpiry)
- {
- if (utcExpiry == DateTime.MaxValue)
- return Cache;
-
- // Round up to seconds ([OutputCache] duration is in seconds).
- var expirySeconds = (long) Math.Round((utcExpiry - DateTime.UtcNow).TotalSeconds);
-
- if (expirySeconds < 0)
- expirySeconds = 0;
-
- ICache<string, object> expiryCache;
-
- if (_expiryCaches.TryGetValue(expirySeconds, out expiryCache))
- return expiryCache;
-
- lock (_syncRoot)
- {
- if (_expiryCaches.TryGetValue(expirySeconds, out expiryCache))
- return expiryCache;
-
- // Copy on write with size limit
- _expiryCaches = _expiryCaches.Count > MaxExpiryCaches
- ? new Dictionary<long, ICache<string, object>>()
- : new Dictionary<long, ICache<string, object>>(_expiryCaches);
-
- expiryCache = Cache.WithExpiryPolicy(new ExpiryPolicy(TimeSpan.FromSeconds(expirySeconds), null, null));
+ var holder = _expiryCacheHolder;
- _expiryCaches[expirySeconds] = expiryCache;
+ if (holder == null)
+ throw new InvalidOperationException(GetType() + " has not been initialized.");
- return expiryCache;
+ return holder.Cache;
}
}
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteSessionStateStoreProvider.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteSessionStateStoreProvider.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteSessionStateStoreProvider.cs
new file mode 100644
index 0000000..1ee6d92
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/IgniteSessionStateStoreProvider.cs
@@ -0,0 +1,494 @@
+\ufeff/*
+ * 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.
+ */
+
+namespace Apache.Ignite.AspNet
+{
+ using System;
+ using System.Collections.Specialized;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Globalization;
+ using System.Threading;
+ using System.Web;
+ using System.Web.SessionState;
+ using Apache.Ignite.AspNet.Impl;
+ using Apache.Ignite.Core;
+ using Apache.Ignite.Core.Binary;
+ using Apache.Ignite.Core.Cache;
+ using Apache.Ignite.Core.Common;
+ using Apache.Ignite.Core.Impl.Cache;
+
+ /// <summary>
+ /// ASP.NET Session-State Store Provider that uses Ignite distributed cache as an underlying storage.
+ /// <para />
+ /// You can either start Ignite yourself, and provide <c>gridName</c> attribute,
+ /// or provide <c>igniteConfigurationSectionName</c> attribute to start Ignite automatically from specified
+ /// configuration section (see <see cref="IgniteConfigurationSection"/>)
+ /// using <c>igniteConfigurationSectionName</c>.
+ /// <para />
+ /// <c>cacheName</c> attribute specifies Ignite cache name to use for data storage. This attribute can be omitted
+ /// if cache name is null.
+ /// <para />
+ /// Optional <c>applicationId</c> attribute allows sharing a single Ignite cache between multiple web applications.
+ /// </summary>
+ public class IgniteSessionStateStoreProvider : SessionStateStoreProviderBase
+ {
+ /** Extension id */
+ private const int ExtensionId = 0;
+
+ /// <summary>
+ /// Op codes for <see cref="ICacheInternal.DoOutInOpExtension{T}"/>.
+ /// </summary>
+ private enum Op
+ {
+ /** Lock the session data. */
+ Lock = 1,
+
+ /** Update and unlock the session data. */
+ SetAndUnlock = 2,
+
+ /** Get the data without lock. */
+ Get = 3,
+
+ /** Put the data without lock. */
+ Put = 4,
+
+ /** Remove the data without lock. */
+ Remove = 5
+ }
+
+ /** Application id config parameter. */
+ private const string ApplicationId = "applicationId";
+
+ /** */
+ private volatile string _applicationId;
+
+ /** */
+ private volatile ExpiryCacheHolder<string, IgniteSessionStateStoreData> _expiryCacheHolder;
+
+ /** */
+ private static long _lockId;
+
+ /// <summary>
+ /// Initializes the provider.
+ /// </summary>
+ /// <param name="name">The friendly name of the provider.</param>
+ /// <param name="config">A collection of the name/value pairs representing the provider-specific attributes
+ /// specified in the configuration for this provider.</param>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ public override void Initialize(string name, NameValueCollection config)
+ {
+ base.Initialize(name, config);
+
+ var cache = ConfigUtil.InitializeCache<string, IgniteSessionStateStoreData>(config, GetType());
+
+ _expiryCacheHolder = new ExpiryCacheHolder<string, IgniteSessionStateStoreData>(cache);
+
+ _applicationId = config[ApplicationId];
+ }
+
+
+ /// <summary>
+ /// Releases all resources used by the <see cref="T:System.Web.SessionState.SessionStateStoreProviderBase" />
+ /// implementation.
+ /// </summary>
+ public override void Dispose()
+ {
+ // No-op.
+ }
+
+ /// <summary>
+ /// Sets a reference to the <see cref="T:System.Web.SessionState.SessionStateItemExpireCallback" />
+ /// delegate for the Session_OnEnd event defined in the Global.asax file.
+ /// </summary>
+ /// <param name="expireCallback">The <see cref="T:System.Web.SessionState.SessionStateItemExpireCallback" />
+ /// delegate for the Session_OnEnd event defined in the Global.asax file.</param>
+ /// <returns>
+ /// true if the session-state store provider supports calling the Session_OnEnd event; otherwise, false.
+ /// </returns>
+ public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
+ {
+ // Expiration events not supported for now.
+ return false;
+ }
+
+ /// <summary>
+ /// Called by the <see cref="T:System.Web.SessionState.SessionStateModule" /> object
+ /// for per-request initialization.
+ /// </summary>
+ /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
+ public override void InitializeRequest(HttpContext context)
+ {
+ // No-op.
+ }
+
+ /// <summary>
+ /// Returns read-only session-state data from the session data store.
+ /// </summary>
+ /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
+ /// <param name="id">The <see cref="P:System.Web.SessionState.HttpSessionState.SessionID" /> for the
+ /// current request.</param>
+ /// <param name="locked">When this method returns, contains a Boolean value that is set to true if the
+ /// requested session item is locked at the session data store; otherwise, false.</param>
+ /// <param name="lockAge">When this method returns, contains a <see cref="T:System.TimeSpan" /> object that
+ /// is set to the amount of time that an item in the session data store has been locked.</param>
+ /// <param name="lockId">When this method returns, contains an object that is set to the lock identifier
+ /// for the current request. For details on the lock identifier, see "Locking Session-Store Data"
+ /// in the <see cref="T:System.Web.SessionState.SessionStateStoreProviderBase" /> class summary.</param>
+ /// <param name="actions">When this method returns, contains one of the
+ /// <see cref="T:System.Web.SessionState.SessionStateActions" /> values, indicating whether the current
+ /// session is an uninitialized, cookieless session.</param>
+ /// <returns>
+ /// A <see cref="T:System.Web.SessionState.SessionStateStoreData" /> populated with session values and
+ /// information from the session data store.
+ /// </returns>
+ public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked,
+ out TimeSpan lockAge, out object lockId,
+ out SessionStateActions actions)
+ {
+ actions = SessionStateActions.None;
+ lockId = null;
+ lockAge = TimeSpan.Zero;
+ locked = false;
+
+ var key = GetKey(id);
+ var data = GetItem(key);
+
+ if (data != null)
+ {
+ locked = data.LockNodeId != null;
+
+ if (!locked)
+ {
+ return data;
+ }
+
+ Debug.Assert(data.LockTime != null);
+
+ lockAge = DateTime.UtcNow - data.LockTime.Value;
+
+ lockId = data.LockId;
+
+ return null;
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Returns read-only session-state data from the session data store.
+ /// </summary>
+ /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
+ /// <param name="id">The <see cref="P:System.Web.SessionState.HttpSessionState.SessionID" /> for the current
+ /// request.</param>
+ /// <param name="locked">When this method returns, contains a Boolean value that is set to true if a lock
+ /// is successfully obtained; otherwise, false.</param>
+ /// <param name="lockAge">When this method returns, contains a <see cref="T:System.TimeSpan" /> object that
+ /// is set to the amount of time that an item in the session data store has been locked.</param>
+ /// <param name="lockId">When this method returns, contains an object that is set to the lock identifier
+ /// for the current request. For details on the lock identifier, see "Locking Session-Store Data" in
+ /// the <see cref="T:System.Web.SessionState.SessionStateStoreProviderBase" /> class summary.</param>
+ /// <param name="actions">When this method returns, contains one of the
+ /// <see cref="T:System.Web.SessionState.SessionStateActions" /> values, indicating whether the current
+ /// session is an uninitialized, cookieless session.</param>
+ /// <returns>
+ /// A <see cref="T:System.Web.SessionState.SessionStateStoreData" /> populated with session values
+ /// and information from the session data store.
+ /// </returns>
+ public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked,
+ out TimeSpan lockAge,
+ out object lockId, out SessionStateActions actions)
+ {
+ actions = SessionStateActions.None; // Our items never need initialization.
+ lockAge = TimeSpan.Zero;
+ lockId = null;
+
+ var lockId0 = Interlocked.Increment(ref _lockId);
+
+ var key = GetKey(id);
+
+ var lockResult = LockItem(key, lockId0);
+
+ // No item found.
+ if (lockResult == null)
+ {
+ locked = false;
+
+ return null;
+ }
+
+ // Item was already locked.
+ if (!lockResult.Success)
+ {
+ locked = true;
+
+ Debug.Assert(lockResult.LockTime != null);
+
+ lockAge = DateTime.UtcNow - lockResult.LockTime.Value;
+ lockId = lockResult.LockId;
+
+ return null;
+ }
+
+ // Item found and lock obtained.
+ locked = false;
+ lockId = lockId0;
+
+ if (lockId0 != lockResult.Data.LockId)
+ throw new IgniteException(string.Format(CultureInfo.InvariantCulture,
+ "Invalid session state lock result, " +
+ "expected lockId: {0}, actual: {1}", lockId0, lockResult.Data.LockId));
+
+ return lockResult.Data;
+ }
+
+ /// <summary>
+ /// Releases a lock on an item in the session data store.
+ /// </summary>
+ /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
+ /// <param name="id">The session identifier for the current request.</param>
+ /// <param name="lockId">The lock identifier for the current request.</param>
+ public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
+ {
+ UnlockItem(GetKey(id), (long) lockId);
+ }
+
+ /// <summary>
+ /// Updates the session-item information in the session-state data store with values from the current request,
+ /// and clears the lock on the data.
+ /// </summary>
+ /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
+ /// <param name="id">The session identifier for the current request.</param>
+ /// <param name="item">The <see cref="T:System.Web.SessionState.SessionStateStoreData" /> object that
+ /// contains the current session values to be stored.</param>
+ /// <param name="lockId">The lock identifier for the current request.</param>
+ /// <param name="newItem">true to identify the session item as a new item; false to identify the session
+ /// item as an existing item.</param>
+ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
+ public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item,
+ object lockId, bool newItem)
+ {
+ Debug.Assert(item != null);
+
+ var key = GetKey(id);
+
+ var data = (IgniteSessionStateStoreData) item;
+
+ if (!(lockId is long) || data.LockId != (long) lockId)
+ throw new IgniteException(string.Format(CultureInfo.InvariantCulture,
+ "Invalid session release request, expected lockId: {0}, actual: {1}", data.LockId, lockId));
+
+ SetAndUnlockItem(key, data);
+ }
+
+ /// <summary>
+ /// Deletes item data from the session data store.
+ /// </summary>
+ /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
+ /// <param name="id">The session identifier for the current request.</param>
+ /// <param name="lockId">The lock identifier for the current request.</param>
+ /// <param name="item">The <see cref="T:System.Web.SessionState.SessionStateStoreData" /> that represents
+ /// the item to delete from the data store.</param>
+ public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
+ {
+ RemoveItem(GetKey(id));
+ }
+
+ /// <summary>
+ /// Updates the expiration date and time of an item in the session data store.
+ /// </summary>
+ /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
+ /// <param name="id">The session identifier for the current request.</param>
+ public override void ResetItemTimeout(HttpContext context, string id)
+ {
+ // No-op.
+
+ // This is not necessary since ResetItemTimeout is called right after SetAndReleaseItemExclusive,
+ // which itself resets the timeout when the item is inserted into cache.
+ }
+
+ /// <summary>
+ /// Creates a new <see cref="T:System.Web.SessionState.SessionStateStoreData" /> object to be used
+ /// for the current request.
+ /// </summary>
+ /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
+ /// <param name="timeout">The session-state <see cref="P:System.Web.SessionState.HttpSessionState.Timeout" />
+ /// value for the new <see cref="T:System.Web.SessionState.SessionStateStoreData" />.</param>
+ /// <returns>
+ /// A new <see cref="T:System.Web.SessionState.SessionStateStoreData" /> for the current request.
+ /// </returns>
+ public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
+ {
+ return new IgniteSessionStateStoreData(SessionStateUtility.GetSessionStaticObjects(context), timeout);
+ }
+
+ /// <summary>
+ /// Adds a new session-state item to the data store.
+ /// </summary>
+ /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
+ /// <param name="id">The <see cref="P:System.Web.SessionState.HttpSessionState.SessionID" />
+ /// for the current request.</param>
+ /// <param name="timeout">The session <see cref="P:System.Web.SessionState.HttpSessionState.Timeout" />
+ /// for the current request.</param>
+ public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
+ {
+ var cache = _expiryCacheHolder.GetCacheWithExpiry((long) timeout * 60);
+
+ var key = GetKey(id);
+
+ var data = new IgniteSessionStateStoreData(SessionStateUtility.GetSessionStaticObjects(context), timeout);
+
+ PutItem(key, data, cache);
+ }
+
+ /// <summary>
+ /// Called by the <see cref="T:System.Web.SessionState.SessionStateModule" /> object at the end of a request.
+ /// </summary>
+ /// <param name="context">The <see cref="T:System.Web.HttpContext" /> for the current request.</param>
+ public override void EndRequest(HttpContext context)
+ {
+ // No-op.
+ }
+
+ /// <summary>
+ /// Gets the cache.
+ /// </summary>
+ private ICache<string, IgniteSessionStateStoreData> Cache
+ {
+ get
+ {
+ var holder = _expiryCacheHolder;
+
+ if (holder == null)
+ throw new InvalidOperationException(GetType() + " has not been initialized.");
+
+ return holder.Cache;
+ }
+ }
+
+ /// <summary>
+ /// Gets the key.
+ /// </summary>
+ private string GetKey(string sessionId)
+ {
+ return _applicationId == null ? sessionId : ApplicationId + "." + sessionId;
+ }
+
+ /// <summary>
+ /// Writes the lock info.
+ /// </summary>
+ private void WriteLockInfo(IBinaryRawWriter writer, long lockId, bool writeTime = false)
+ {
+ writer.WriteGuid(Cache.Ignite.GetCluster().GetLocalNode().Id);
+ writer.WriteLong(lockId);
+
+ if (writeTime)
+ writer.WriteTimestamp(DateTime.UtcNow);
+ }
+
+ /// <summary>
+ /// Locks the item.
+ /// </summary>
+ private SessionStateLockResult LockItem(string key, long lockId)
+ {
+ return OutInOp(Op.Lock,
+ w =>
+ {
+ w.WriteString(key);
+ WriteLockInfo(w, lockId, true);
+ },
+ r => new SessionStateLockResult(r));
+ }
+
+ /// <summary>
+ /// Unlocks the item.
+ /// </summary>
+ private void UnlockItem(string key, long lockId)
+ {
+ OutOp(Op.SetAndUnlock,
+ w =>
+ {
+ w.WriteString(key);
+ w.WriteBoolean(false); // Only unlock.
+ WriteLockInfo(w, lockId);
+ });
+ }
+
+ /// <summary>
+ /// Sets and unlocks the item.
+ /// </summary>
+ private void SetAndUnlockItem(string key, IgniteSessionStateStoreData data)
+ {
+ var cache = _expiryCacheHolder.GetCacheWithExpiry(data.Timeout * 60);
+
+ OutOp(Op.SetAndUnlock, w =>
+ {
+ w.WriteString(key);
+ w.WriteBoolean(true); // Unlock and update.
+ data.WriteBinary(w, true);
+ }, cache);
+ }
+
+ /// <summary>
+ /// Puts the item.
+ /// </summary>
+ private void PutItem(string key, IgniteSessionStateStoreData data, ICache<string, IgniteSessionStateStoreData> cache)
+ {
+ OutOp(Op.Put, w =>
+ {
+ w.WriteString(key);
+ data.WriteBinary(w, false);
+ }, cache);
+ }
+
+ /// <summary>
+ /// Gets the item.
+ /// </summary>
+ private IgniteSessionStateStoreData GetItem(string key)
+ {
+ return OutInOp(Op.Get, w => w.WriteString(key), r => new IgniteSessionStateStoreData(r));
+ }
+
+ /// <summary>
+ /// Removes the item.
+ /// </summary>
+ private void RemoveItem(string key)
+ {
+ OutOp(Op.Remove, w => w.WriteString(key));
+ }
+
+ /// <summary>
+ /// Invokes the extension operation.
+ /// </summary>
+ private void OutOp(Op op, Action<IBinaryRawWriter> writeAction,
+ ICache<string, IgniteSessionStateStoreData> cache = null)
+ {
+ OutInOp<object>(op, writeAction, null, cache);
+ }
+
+ /// <summary>
+ /// Invokes the extension operation.
+ /// </summary>
+ private T OutInOp<T>(Op op, Action<IBinaryRawWriter> writeAction, Func<IBinaryRawReader, T> readFunc,
+ ICache<string, IgniteSessionStateStoreData> cache = null)
+ {
+ cache = cache ?? Cache;
+
+ return ((ICacheInternal) cache).DoOutInOpExtension(ExtensionId, (int) op, writeAction, readFunc);
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ConfigUtil.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ConfigUtil.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ConfigUtil.cs
new file mode 100644
index 0000000..3eb3d90
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ConfigUtil.cs
@@ -0,0 +1,109 @@
+\ufeff/*
+ * 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.
+ */
+
+namespace Apache.Ignite.AspNet.Impl
+{
+ using System;
+ using System.Collections.Specialized;
+ using System.Configuration;
+ using System.Diagnostics;
+ using System.Globalization;
+ using Apache.Ignite.Core;
+ using Apache.Ignite.Core.Cache;
+ using Apache.Ignite.Core.Cache.Configuration;
+ using Apache.Ignite.Core.Common;
+
+ /// <summary>
+ /// Config utils.
+ /// </summary>
+ internal static class ConfigUtil
+ {
+ /** */
+ public const string GridName = "gridName";
+
+ /** */
+ public const string CacheName = "cacheName";
+
+ /** */
+ public const string IgniteConfigurationSectionName = "igniteConfigurationSectionName";
+
+ /// <summary>
+ /// Initializes the cache from configuration.
+ /// </summary>
+ public static ICache<TK, TV> InitializeCache<TK, TV>(NameValueCollection config, Type callerType)
+ {
+ Debug.Assert(config != null);
+ Debug.Assert(callerType != null);
+
+ var gridName = config[GridName];
+ var cacheName = config[CacheName];
+ var cfgSection = config[IgniteConfigurationSectionName];
+
+ try
+ {
+ var grid = StartFromApplicationConfiguration(cfgSection, gridName);
+
+ var cacheConfiguration = new CacheConfiguration(cacheName);
+
+ return grid.GetOrCreateCache<TK, TV>(cacheConfiguration);
+ }
+ catch (Exception ex)
+ {
+ throw new IgniteException(string.Format(CultureInfo.InvariantCulture,
+ "Failed to initialize {0}: {1}", callerType, ex), ex);
+ }
+
+ }
+
+ /// <summary>
+ /// Starts Ignite from application configuration.
+ /// </summary>
+ private static IIgnite StartFromApplicationConfiguration(string sectionName, string gridName)
+ {
+ IgniteConfiguration config;
+
+ if (!string.IsNullOrEmpty(sectionName))
+ {
+ var section = ConfigurationManager.GetSection(sectionName) as IgniteConfigurationSection;
+
+ if (section == null)
+ throw new ConfigurationErrorsException(string.Format(CultureInfo.InvariantCulture,
+ "Could not find {0} with name '{1}'", typeof(IgniteConfigurationSection).Name, sectionName));
+
+ config = section.IgniteConfiguration;
+ }
+ else
+ config = new IgniteConfiguration {GridName = gridName};
+
+ // Check if already started.
+ var ignite = Ignition.TryGetIgnite(config.GridName);
+
+ if (ignite != null)
+ return ignite;
+
+ // Start.
+ if (string.IsNullOrWhiteSpace(config.IgniteHome))
+ {
+ // IgniteHome not set by user: populate from default directory.
+ config = new IgniteConfiguration(config) { IgniteHome = IgniteWebUtils.GetWebIgniteHome() };
+ }
+
+ return Ignition.Start(config);
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ExpiryCacheHolder.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ExpiryCacheHolder.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ExpiryCacheHolder.cs
new file mode 100644
index 0000000..9678c38
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet/Impl/ExpiryCacheHolder.cs
@@ -0,0 +1,113 @@
+\ufeff/*
+ * 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.
+ */
+
+namespace Apache.Ignite.AspNet.Impl
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using Apache.Ignite.Core.Cache;
+ using Apache.Ignite.Core.Cache.Expiry;
+
+ /// <summary>
+ /// Holds WithExpiry caches per expiration interval to avoid garbage on frequent WithExpiry calls.
+ /// </summary>
+ internal class ExpiryCacheHolder<TK, TV>
+ {
+ /** Max number of cached expiry caches. */
+ private const int MaxExpiryCaches = 1000;
+
+ /** */
+ private readonly ICache<TK, TV> _cache;
+
+ /** Cached caches per expiry seconds. */
+ private volatile Dictionary<long, ICache<TK, TV>> _expiryCaches =
+ new Dictionary<long, ICache<TK, TV>>();
+
+ /** Sync object. */
+ private readonly object _syncRoot = new object();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ExpiryCacheHolder{TK, TV}"/> class.
+ /// </summary>
+ /// <param name="cache">The cache.</param>
+ public ExpiryCacheHolder(ICache<TK, TV> cache)
+ {
+ Debug.Assert(cache != null);
+
+ _cache = cache;
+ }
+
+ /// <summary>
+ /// Gets the cache.
+ /// </summary>
+ public ICache<TK, TV> Cache
+ {
+ get { return _cache; }
+ }
+
+ /// <summary>
+ /// Gets the cache with expiry policy according to provided expiration date.
+ /// </summary>
+ /// <param name="utcExpiry">The UTC expiry.</param>
+ /// <returns>Cache with expiry policy.</returns>
+ public ICache<TK, TV> GetCacheWithExpiry(DateTime utcExpiry)
+ {
+ if (utcExpiry == DateTime.MaxValue)
+ return _cache;
+
+ Debug.Assert(utcExpiry.Kind == DateTimeKind.Utc);
+
+ // Round up to seconds ([OutputCache] duration is in seconds).
+ var expirySeconds = (long)Math.Round((utcExpiry - DateTime.UtcNow).TotalSeconds);
+
+ if (expirySeconds < 0)
+ expirySeconds = 0;
+
+ return GetCacheWithExpiry(expirySeconds);
+ }
+
+ /// <summary>
+ /// Gets the cache with expiry.
+ /// </summary>
+ /// <param name="expiry">The expiration interval (in seconds).</param>
+ public ICache<TK, TV> GetCacheWithExpiry(long expiry)
+ {
+ ICache<TK, TV> expiryCache;
+
+ if (_expiryCaches.TryGetValue(expiry, out expiryCache))
+ return expiryCache;
+
+ lock (_syncRoot)
+ {
+ if (_expiryCaches.TryGetValue(expiry, out expiryCache))
+ return expiryCache;
+
+ // Copy on write with size limit
+ _expiryCaches = _expiryCaches.Count > MaxExpiryCaches
+ ? new Dictionary<long, ICache<TK, TV>>()
+ : new Dictionary<long, ICache<TK, TV>>(_expiryCaches);
+
+ expiryCache = Cache.WithExpiryPolicy(new ExpiryPolicy(TimeSpan.FromSeconds(expiry), null, null));
+
+ _expiryCaches[expiry] = expiryCache;
+
+ return expiryCache;
+ }
+ }
+ }
+}