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:53 UTC

[11/13] ignite git commit: IGNITE-3199 .NET: Add ASP.NET Session-State Store Provider

IGNITE-3199 .NET: Add ASP.NET Session-State Store Provider


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/799f1909
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/799f1909
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/799f1909

Branch: refs/heads/ignite-comm-opts2
Commit: 799f1909cea96037a38eee1b4ecb21fab737d092
Parents: 886ed64
Author: Pavel Tupitsyn <pt...@apache.org>
Authored: Fri Sep 16 17:50:24 2016 +0300
Committer: Pavel Tupitsyn <pt...@apache.org>
Committed: Fri Sep 16 17:50:24 2016 +0300

----------------------------------------------------------------------
 .../ignite/internal/binary/BinaryContext.java   |   5 +
 .../platform/PlatformConfigurationEx.java       |   7 +
 .../platform/PlatformContextImpl.java           |   3 -
 .../processors/platform/PlatformProcessor.java  |  10 +-
 .../platform/PlatformProcessorImpl.java         |  72 ++-
 .../platform/cache/PlatformCache.java           | 114 +++-
 .../platform/cache/PlatformCacheExtension.java  |  47 ++
 .../cpp/PlatformCppConfigurationEx.java         |   7 +
 .../dotnet/PlatformDotNetConfigurationEx.java   |   9 +
 .../PlatformDotNetSessionCacheExtension.java    | 144 +++++
 .../websession/PlatformDotNetSessionData.java   | 260 +++++++++
 .../PlatformDotNetSessionLockProcessor.java     |  84 +++
 .../PlatformDotNetSessionLockResult.java        | 106 ++++
 ...tformDotNetSessionSetAndUnlockProcessor.java | 179 +++++++
 .../Apache.Ignite.AspNet.Tests.csproj           |  77 +++
 .../Apache.Ignite.AspNet.Tests.snk              | Bin 0 -> 596 bytes
 .../Apache.Ignite.AspNet.Tests/App.config       |  72 +++
 .../ExpiryCacheHolderTest.cs                    | 492 +++++++++++++++++
 .../IgniteOutputCacheProviderTest.cs            | 172 ++++++
 .../IgniteSessionStateItemCollectionTest.cs     | 267 ++++++++++
 .../IgniteSessionStateStoreDataTest.cs          | 117 ++++
 .../IgniteSessionStateStoreProviderTest.cs      | 425 +++++++++++++++
 .../Properties/AssemblyInfo.cs                  |  42 ++
 .../Apache.Ignite.AspNet.Tests/packages.config  |  22 +
 .../Apache.Ignite.AspNet.csproj                 |   6 +
 .../Apache.Ignite.AspNet.ruleset                |   3 +
 .../IgniteOutputCacheProvider.cs                | 120 +----
 .../IgniteSessionStateStoreProvider.cs          | 494 +++++++++++++++++
 .../Apache.Ignite.AspNet/Impl/ConfigUtil.cs     | 109 ++++
 .../Impl/ExpiryCacheHolder.cs                   | 113 ++++
 .../Impl/IgniteSessionStateItemCollection.cs    | 534 +++++++++++++++++++
 .../Impl/IgniteSessionStateStoreData.cs         | 116 ++++
 .../Impl/SessionStateLockResult.cs              |  91 ++++
 .../Properties/AssemblyInfo.cs                  |   5 +-
 .../Apache.Ignite.Core.Tests.csproj             |   6 -
 .../AspNet/IgniteOutputCacheProviderTest.cs     | 172 ------
 .../Apache.Ignite.Core.Tests/TestUtils.cs       |   1 -
 .../Apache.Ignite.Core/Impl/Cache/CacheImpl.cs  |  15 +
 .../Apache.Ignite.Core/Impl/Cache/CacheOp.cs    |   3 +-
 .../Impl/Cache/ICacheInternal.cs                |  14 +
 .../Impl/Unmanaged/UnmanagedCallbacks.cs        |   1 -
 .../Properties/AssemblyInfo.cs                  |   1 +
 modules/platforms/dotnet/Apache.Ignite.sln      |  14 +
 43 files changed, 4223 insertions(+), 328 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
index 0d66970..c468a4d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
@@ -76,6 +76,8 @@ import org.apache.ignite.internal.processors.igfs.meta.IgfsMetaFileUnlockProcess
 import org.apache.ignite.internal.processors.igfs.meta.IgfsMetaUpdatePropertiesProcessor;
 import org.apache.ignite.internal.processors.igfs.meta.IgfsMetaUpdateTimesProcessor;
 import org.apache.ignite.internal.processors.platform.PlatformJavaObjectFactoryProxy;
+import org.apache.ignite.internal.processors.platform.websession.PlatformDotNetSessionData;
+import org.apache.ignite.internal.processors.platform.websession.PlatformDotNetSessionLockResult;
 import org.apache.ignite.internal.util.lang.GridMapEntry;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.T2;
@@ -321,6 +323,9 @@ public class BinaryContext {
         registerPredefinedType(BinaryMetadata.class, 0);
         registerPredefinedType(BinaryEnumObjectImpl.class, 0);
 
+        registerPredefinedType(PlatformDotNetSessionData.class, 0);
+        registerPredefinedType(PlatformDotNetSessionLockResult.class, 0);
+
         // IDs range [200..1000] is used by Ignite internal APIs.
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformConfigurationEx.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformConfigurationEx.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformConfigurationEx.java
index 66eff8b..b7c8895 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformConfigurationEx.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformConfigurationEx.java
@@ -17,8 +17,10 @@
 
 package org.apache.ignite.internal.processors.platform;
 
+import org.apache.ignite.internal.processors.platform.cache.PlatformCacheExtension;
 import org.apache.ignite.internal.processors.platform.callback.PlatformCallbackGateway;
 import org.apache.ignite.internal.processors.platform.memory.PlatformMemoryManagerImpl;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
 
@@ -45,4 +47,9 @@ public interface PlatformConfigurationEx {
      * @return Warnings to be displayed on grid start.
      */
     public Collection<String> warnings();
+
+    /**
+     * @return Available cache extensions.
+     */
+    @Nullable public Collection<PlatformCacheExtension> cacheExtensions();
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContextImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContextImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContextImpl.java
index d544fff..e7fdb0a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContextImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformContextImpl.java
@@ -118,9 +118,6 @@ public class PlatformContextImpl implements PlatformContext {
     /** Platform name. */
     private final String platform;
 
-    /**
-     * Static initializer.
-     */
     static {
         Set<Integer> evtTyps0 = new HashSet<>();
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformProcessor.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformProcessor.java
index fc42b68..f0201ef 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformProcessor.java
@@ -19,7 +19,6 @@ package org.apache.ignite.internal.processors.platform;
 
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCheckedException;
-import org.apache.ignite.IgniteException;
 import org.apache.ignite.internal.processors.GridProcessor;
 import org.apache.ignite.internal.processors.platform.cache.store.PlatformCacheStore;
 import org.jetbrains.annotations.Nullable;
@@ -203,9 +202,8 @@ public interface PlatformProcessor extends GridProcessor {
      * @param initVal Initial value.
      * @param create Create flag.
      * @return Platform atomic long.
-     * @throws IgniteException
      */
-    public PlatformTarget atomicLong(String name, long initVal, boolean create) throws IgniteException;
+    public PlatformTarget atomicLong(String name, long initVal, boolean create);
 
     /**
      * Get or create AtomicSequence.
@@ -213,9 +211,8 @@ public interface PlatformProcessor extends GridProcessor {
      * @param initVal Initial value.
      * @param create Create flag.
      * @return Platform atomic long.
-     * @throws IgniteException
      */
-    public PlatformTarget atomicSequence(String name, long initVal, boolean create) throws IgniteException;
+    public PlatformTarget atomicSequence(String name, long initVal, boolean create);
 
     /**
      * Get or create AtomicReference.
@@ -223,9 +220,8 @@ public interface PlatformProcessor extends GridProcessor {
      * @param memPtr Pointer to a stream with initial value. 0 for null initial value.
      * @param create Create flag.
      * @return Platform atomic long.
-     * @throws IgniteException
      */
-    public PlatformTarget atomicReference(String name, long memPtr, boolean create) throws IgniteException;
+    public PlatformTarget atomicReference(String name, long memPtr, boolean create);
 
     /**
      * Gets the configuration of the current Ignite instance.

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformProcessorImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformProcessorImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformProcessorImpl.java
index 8c9e205..b364c4a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformProcessorImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/PlatformProcessorImpl.java
@@ -35,6 +35,7 @@ import org.apache.ignite.internal.processors.cache.IgniteCacheProxy;
 import org.apache.ignite.internal.processors.datastreamer.DataStreamerImpl;
 import org.apache.ignite.internal.processors.datastructures.GridCacheAtomicLongImpl;
 import org.apache.ignite.internal.processors.platform.cache.PlatformCache;
+import org.apache.ignite.internal.processors.platform.cache.PlatformCacheExtension;
 import org.apache.ignite.internal.processors.platform.cache.affinity.PlatformAffinity;
 import org.apache.ignite.internal.processors.platform.cache.store.PlatformCacheStore;
 import org.apache.ignite.internal.processors.platform.cluster.PlatformClusterGroup;
@@ -59,6 +60,8 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.locks.ReadWriteLock;
@@ -79,6 +82,7 @@ public class PlatformProcessorImpl extends GridProcessorAdapter implements Platf
     private final ReadWriteLock storeLock = new ReentrantReadWriteLock();
 
     /** Logger. */
+    @SuppressWarnings("FieldCanBeLocal")
     private final IgniteLogger log;
 
     /** Context. */
@@ -93,6 +97,9 @@ public class PlatformProcessorImpl extends GridProcessorAdapter implements Platf
     /** Whether processor if stopped (or stopping). */
     private volatile boolean stopped;
 
+    /** Cache extensions. */
+    private final PlatformCacheExtension[] cacheExts;
+
     /**
      * Constructor.
      *
@@ -118,6 +125,9 @@ public class PlatformProcessorImpl extends GridProcessorAdapter implements Platf
         }
 
         platformCtx = new PlatformContextImpl(ctx, interopCfg.gate(), interopCfg.memory(), interopCfg.platform());
+
+        // Initialize cache extensions (if any).
+        cacheExts = prepareCacheExtensions(interopCfg.cacheExtensions());
     }
 
     /** {@inheritDoc} */
@@ -207,7 +217,7 @@ public class PlatformProcessorImpl extends GridProcessorAdapter implements Platf
         if (cache == null)
             throw new IllegalArgumentException("Cache doesn't exist: " + name);
 
-        return new PlatformCache(platformCtx, cache.keepBinary(), false);
+        return createPlatformCache(cache);
     }
 
     /** {@inheritDoc} */
@@ -216,7 +226,7 @@ public class PlatformProcessorImpl extends GridProcessorAdapter implements Platf
 
         assert cache != null;
 
-        return new PlatformCache(platformCtx, cache.keepBinary(), false);
+        return createPlatformCache(cache);
     }
 
     /** {@inheritDoc} */
@@ -225,7 +235,7 @@ public class PlatformProcessorImpl extends GridProcessorAdapter implements Platf
 
         assert cache != null;
 
-        return new PlatformCache(platformCtx, cache.keepBinary(), false);
+        return createPlatformCache(cache);
     }
 
     /** {@inheritDoc} */
@@ -237,7 +247,7 @@ public class PlatformProcessorImpl extends GridProcessorAdapter implements Platf
             ? (IgniteCacheProxy)ctx.grid().createCache(cfg, PlatformConfigurationUtils.readNearConfiguration(reader))
             : (IgniteCacheProxy)ctx.grid().createCache(cfg);
 
-        return new PlatformCache(platformCtx, cache.keepBinary(), false);
+        return createPlatformCache(cache);
     }
 
     /** {@inheritDoc} */
@@ -250,7 +260,7 @@ public class PlatformProcessorImpl extends GridProcessorAdapter implements Platf
                     PlatformConfigurationUtils.readNearConfiguration(reader))
             : (IgniteCacheProxy)ctx.grid().getOrCreateCache(cfg);
 
-        return new PlatformCache(platformCtx, cache.keepBinary(), false);
+        return createPlatformCache(cache);
     }
 
     /** {@inheritDoc} */
@@ -404,7 +414,7 @@ public class PlatformProcessorImpl extends GridProcessorAdapter implements Platf
 
         IgniteCacheProxy cache = (IgniteCacheProxy)ctx.grid().createNearCache(cacheName, cfg);
 
-        return new PlatformCache(platformCtx, cache.keepBinary(), false);
+        return createPlatformCache(cache);
     }
 
     /** {@inheritDoc} */
@@ -413,7 +423,14 @@ public class PlatformProcessorImpl extends GridProcessorAdapter implements Platf
 
         IgniteCacheProxy cache = (IgniteCacheProxy)ctx.grid().getOrCreateNearCache(cacheName, cfg);
 
-        return new PlatformCache(platformCtx, cache.keepBinary(), false);
+        return createPlatformCache(cache);
+    }
+
+    /**
+     * Creates new platform cache.
+     */
+    private PlatformTarget createPlatformCache(IgniteCacheProxy cache) {
+        return new PlatformCache(platformCtx, cache, false, cacheExts);
     }
 
     /**
@@ -447,6 +464,47 @@ public class PlatformProcessorImpl extends GridProcessorAdapter implements Platf
     }
 
     /**
+     * Prepare cache extensions.
+     *
+     * @param cacheExts Original extensions.
+     * @return Prepared extensions.
+     */
+    private static PlatformCacheExtension[] prepareCacheExtensions(Collection<PlatformCacheExtension> cacheExts) {
+        if (!F.isEmpty(cacheExts)) {
+            int maxExtId = 0;
+
+            Map<Integer, PlatformCacheExtension> idToExt = new HashMap<>();
+
+            for (PlatformCacheExtension cacheExt : cacheExts) {
+                if (cacheExt == null)
+                    throw new IgniteException("Platform cache extension cannot be null.");
+
+                if (cacheExt.id() < 0)
+                    throw new IgniteException("Platform cache extension ID cannot be negative: " + cacheExt);
+
+                PlatformCacheExtension oldCacheExt = idToExt.put(cacheExt.id(), cacheExt);
+
+                if (oldCacheExt != null)
+                    throw new IgniteException("Platform cache extensions cannot have the same ID [" +
+                        "id=" + cacheExt.id() + ", first=" + oldCacheExt + ", second=" + cacheExt + ']');
+
+                if (cacheExt.id() > maxExtId)
+                    maxExtId = cacheExt.id();
+            }
+
+            PlatformCacheExtension[] res = new PlatformCacheExtension[maxExtId + 1];
+
+            for (PlatformCacheExtension cacheExt : cacheExts)
+                res[cacheExt.id()]= cacheExt;
+
+            return res;
+        }
+        else
+            //noinspection ZeroLengthArrayAllocation
+            return new PlatformCacheExtension[0];
+    }
+
+    /**
      * Store and manager pair.
      */
     private static class StoreInfo {

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cache/PlatformCache.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cache/PlatformCache.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cache/PlatformCache.java
index a7b6e41..a1f8da9 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cache/PlatformCache.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cache/PlatformCache.java
@@ -19,6 +19,8 @@ package org.apache.ignite.internal.processors.platform.cache;
 
 import org.apache.ignite.IgniteCache;
 import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.binary.BinaryRawReader;
 import org.apache.ignite.cache.CacheEntryProcessor;
 import org.apache.ignite.cache.CacheMetrics;
 import org.apache.ignite.cache.CachePartialUpdateException;
@@ -28,7 +30,7 @@ import org.apache.ignite.cache.query.ScanQuery;
 import org.apache.ignite.cache.query.SqlFieldsQuery;
 import org.apache.ignite.cache.query.SqlQuery;
 import org.apache.ignite.cache.query.TextQuery;
-import org.apache.ignite.configuration.*;
+import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.binary.BinaryRawReaderEx;
 import org.apache.ignite.internal.binary.BinaryRawWriterEx;
@@ -52,12 +54,13 @@ import org.apache.ignite.internal.processors.platform.utils.PlatformWriterClosur
 import org.apache.ignite.internal.util.GridConcurrentFactory;
 import org.apache.ignite.internal.util.future.IgniteFutureImpl;
 import org.apache.ignite.internal.util.typedef.C1;
-import org.apache.ignite.lang.IgniteBiInClosure;
 import org.apache.ignite.internal.util.typedef.X;
+import org.apache.ignite.lang.IgniteBiInClosure;
 import org.apache.ignite.lang.IgniteFuture;
 import org.jetbrains.annotations.Nullable;
 
 import javax.cache.Cache;
+import javax.cache.CacheException;
 import javax.cache.expiry.Duration;
 import javax.cache.expiry.ExpiryPolicy;
 import javax.cache.integration.CompletionListener;
@@ -193,20 +196,26 @@ public class PlatformCache extends PlatformAbstractTarget {
     /** */
     public static final int OP_LOAD_ALL = 40;
 
-    /** Underlying JCache. */
+    /** */
+    public static final int OP_EXTENSION = 41;
+
+    /** Underlying JCache in binary mode. */
     private final IgniteCacheProxy cache;
 
+    /** Initial JCache (not in binary mode). */
+    private final IgniteCache rawCache;
+
     /** Whether this cache is created with "keepBinary" flag on the other side. */
     private final boolean keepBinary;
 
     /** */
-    private static final GetAllWriter WRITER_GET_ALL = new GetAllWriter();
+    private static final PlatformFutureUtils.Writer WRITER_GET_ALL = new GetAllWriter();
 
     /** */
-    private static final EntryProcessorInvokeWriter WRITER_INVOKE = new EntryProcessorInvokeWriter();
+    private static final PlatformFutureUtils.Writer WRITER_INVOKE = new EntryProcessorInvokeWriter();
 
     /** */
-    private static final EntryProcessorInvokeAllWriter WRITER_INVOKE_ALL = new EntryProcessorInvokeAllWriter();
+    private static final PlatformFutureUtils.Writer WRITER_INVOKE_ALL = new EntryProcessorInvokeAllWriter();
 
     /** Map with currently active locks. */
     private final ConcurrentMap<Long, Lock> lockMap = GridConcurrentFactory.newMap();
@@ -214,6 +223,9 @@ public class PlatformCache extends PlatformAbstractTarget {
     /** Lock ID sequence. */
     private static final AtomicLong LOCK_ID_GEN = new AtomicLong();
 
+    /** Extensions. */
+    private final PlatformCacheExtension[] exts;
+
     /**
      * Constructor.
      *
@@ -222,10 +234,29 @@ public class PlatformCache extends PlatformAbstractTarget {
      * @param keepBinary Keep binary flag.
      */
     public PlatformCache(PlatformContext platformCtx, IgniteCache cache, boolean keepBinary) {
+        this(platformCtx, cache, keepBinary, new PlatformCacheExtension[0]);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param platformCtx Context.
+     * @param cache Underlying cache.
+     * @param keepBinary Keep binary flag.
+     * @param exts Extensions.
+     */
+    public PlatformCache(PlatformContext platformCtx, IgniteCache cache, boolean keepBinary,
+        PlatformCacheExtension[] exts) {
         super(platformCtx);
 
-        this.cache = (IgniteCacheProxy)cache;
+        assert cache != null;
+        assert exts != null;
+
+        rawCache = cache;
+
+        this.cache = (IgniteCacheProxy)cache.withKeepBinary();
         this.keepBinary = keepBinary;
+        this.exts = exts;
     }
 
     /**
@@ -237,7 +268,7 @@ public class PlatformCache extends PlatformAbstractTarget {
         if (cache.delegate().skipStore())
             return this;
 
-        return new PlatformCache(platformCtx, cache.withSkipStore(), keepBinary);
+        return copy(rawCache.withSkipStore(), keepBinary);
     }
 
     /**
@@ -249,7 +280,7 @@ public class PlatformCache extends PlatformAbstractTarget {
         if (keepBinary)
             return this;
 
-        return new PlatformCache(platformCtx, cache.withKeepBinary(), true);
+        return copy(rawCache.withKeepBinary(), true);
     }
 
     /**
@@ -261,9 +292,9 @@ public class PlatformCache extends PlatformAbstractTarget {
      * @return Cache.
      */
     public PlatformCache withExpiryPolicy(final long create, final long update, final long access) {
-        IgniteCache cache0 = cache.withExpiryPolicy(new InteropExpiryPolicy(create, update, access));
+        IgniteCache cache0 = rawCache.withExpiryPolicy(new InteropExpiryPolicy(create, update, access));
 
-        return new PlatformCache(platformCtx, cache0, keepBinary);
+        return copy(cache0, keepBinary);
     }
 
     /**
@@ -275,7 +306,7 @@ public class PlatformCache extends PlatformAbstractTarget {
         if (cache.isAsync())
             return this;
 
-        return new PlatformCache(platformCtx, (IgniteCache)cache.withAsync(), keepBinary);
+        return copy(rawCache.withAsync(), keepBinary);
     }
 
     /**
@@ -289,11 +320,19 @@ public class PlatformCache extends PlatformAbstractTarget {
         if (opCtx != null && opCtx.noRetries())
             return this;
 
-        return new PlatformCache(platformCtx, cache.withNoRetries(), keepBinary);
+        return copy(rawCache.withNoRetries(), keepBinary);
+    }
+
+    /**
+     * @return Raw cache.
+     */
+    public IgniteCache rawCache() {
+        return rawCache;
     }
 
     /** {@inheritDoc} */
-    @Override protected long processInStreamOutLong(int type, BinaryRawReaderEx reader, PlatformMemory mem) throws IgniteCheckedException {
+    @Override protected long processInStreamOutLong(int type, BinaryRawReaderEx reader, PlatformMemory mem)
+        throws IgniteCheckedException {
         try {
             switch (type) {
                 case OP_PUT:
@@ -452,6 +491,11 @@ public class PlatformCache extends PlatformAbstractTarget {
 
                 case OP_LOCK_ALL:
                     return registerLock(cache.lockAll(PlatformUtils.readCollection(reader)));
+
+                case OP_EXTENSION:
+                    PlatformCacheExtension ext = extension(reader.readInt());
+
+                    return ext.processInOutStreamLong(this, reader.readInt(), reader, mem);
             }
         }
         catch (Exception e) {
@@ -474,14 +518,14 @@ public class PlatformCache extends PlatformAbstractTarget {
     /**
      * Writes the result to reused stream, if any.
      */
-    private long writeResult(PlatformMemory mem, Object obj) {
+    public long writeResult(PlatformMemory mem, Object obj) {
         return writeResult(mem, obj, null);
     }
 
     /**
      * Writes the result to reused stream, if any.
      */
-    private long writeResult(PlatformMemory mem, Object obj, PlatformWriterClosure clo) {
+    public long writeResult(PlatformMemory mem, Object obj, PlatformWriterClosure clo) {
         if (obj == null)
             return FALSE;
 
@@ -665,7 +709,7 @@ public class PlatformCache extends PlatformAbstractTarget {
             return new PlatformCachePartialUpdateException((CachePartialUpdateCheckedException)e, platformCtx, keepBinary);
 
         if (e.getCause() instanceof EntryProcessorException)
-            return (EntryProcessorException) e.getCause();
+            return (Exception)e.getCause();
 
         return super.convertException(e);
     }
@@ -743,7 +787,7 @@ public class PlatformCache extends PlatformAbstractTarget {
      * Clears the contents of the cache, without notifying listeners or CacheWriters.
      *
      * @throws IllegalStateException if the cache is closed.
-     * @throws javax.cache.CacheException if there is a problem during the clear
+     * @throws CacheException if there is a problem during the clear
      */
     public void clear() throws IgniteCheckedException {
         cache.clear();
@@ -752,7 +796,7 @@ public class PlatformCache extends PlatformAbstractTarget {
     /**
      * Removes all entries.
      *
-     * @throws org.apache.ignite.IgniteCheckedException In case of error.
+     * @throws IgniteCheckedException In case of error.
      */
     public void removeAll() throws IgniteCheckedException {
         cache.removeAll();
@@ -969,7 +1013,7 @@ public class PlatformCache extends PlatformAbstractTarget {
     /**
      * Reads text query.
      */
-    private Query readTextQuery(BinaryRawReaderEx reader) {
+    private Query readTextQuery(BinaryRawReader reader) {
         boolean loc = reader.readBoolean();
         String txt = reader.readString();
         String typ = reader.readString();
@@ -1004,6 +1048,34 @@ public class PlatformCache extends PlatformAbstractTarget {
     }
 
     /**
+     * Clones this instance.
+     *
+     * @param cache Cache.
+     * @param keepBinary Keep binary flag.
+     * @return Cloned instance.
+     */
+    private PlatformCache copy(IgniteCache cache, boolean keepBinary) {
+        return new PlatformCache(platformCtx, cache, keepBinary, exts);
+    }
+
+    /**
+     * Get extension by ID.
+     *
+     * @param id ID.
+     * @return Extension.
+     */
+    private PlatformCacheExtension extension(int id) {
+        if (exts != null && id < exts.length) {
+            PlatformCacheExtension ext = exts[id];
+
+            if (ext != null)
+                return ext;
+        }
+
+        throw new IgniteException("Platform cache extension is not registered [id=" + id + ']');
+    }
+
+    /**
      * Writes error with EntryProcessorException cause.
      */
     private static class GetAllWriter implements PlatformFutureUtils.Writer {
@@ -1088,7 +1160,7 @@ public class PlatformCache extends PlatformAbstractTarget {
          * @param update Expiry for update.
          * @param access Expiry for access.
          */
-        public InteropExpiryPolicy(long create, long update, long access) {
+        private InteropExpiryPolicy(long create, long update, long access) {
             this.create = convert(create);
             this.update = convert(update);
             this.access = convert(access);

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cache/PlatformCacheExtension.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cache/PlatformCacheExtension.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cache/PlatformCacheExtension.java
new file mode 100644
index 0000000..5d2040c
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cache/PlatformCacheExtension.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.platform.cache;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.binary.BinaryRawReaderEx;
+import org.apache.ignite.internal.processors.platform.memory.PlatformMemory;
+
+/**
+ * Platform cache extension. Decouples other modules from cache.
+ */
+public interface PlatformCacheExtension {
+    /**
+     * Get extension ID. Must be unique among all extensions.
+     *
+     * @return Extension ID.
+     */
+    public int id();
+
+    /**
+     * Invokes in-out operation with long return type.
+     *
+     * @param target Target cache.
+     * @param type Operation type.
+     * @param reader Reader.
+     * @param mem Memory.
+     * @return Result.
+     * @throws IgniteCheckedException If failed.
+     */
+    long processInOutStreamLong(PlatformCache target, int type, BinaryRawReaderEx reader, PlatformMemory mem)
+        throws IgniteCheckedException;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cpp/PlatformCppConfigurationEx.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cpp/PlatformCppConfigurationEx.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cpp/PlatformCppConfigurationEx.java
index ea11ce9..931a18e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cpp/PlatformCppConfigurationEx.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/cpp/PlatformCppConfigurationEx.java
@@ -18,10 +18,12 @@
 package org.apache.ignite.internal.processors.platform.cpp;
 
 import org.apache.ignite.internal.processors.platform.PlatformConfigurationEx;
+import org.apache.ignite.internal.processors.platform.cache.PlatformCacheExtension;
 import org.apache.ignite.internal.processors.platform.callback.PlatformCallbackGateway;
 import org.apache.ignite.internal.processors.platform.memory.PlatformMemoryManagerImpl;
 import org.apache.ignite.internal.processors.platform.utils.PlatformUtils;
 import org.apache.ignite.platform.cpp.PlatformCppConfiguration;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
 
@@ -73,6 +75,11 @@ public class PlatformCppConfigurationEx extends PlatformCppConfiguration impleme
         return warns;
     }
 
+    /** {@inheritDoc} */
+    @Override @Nullable public Collection<PlatformCacheExtension> cacheExtensions() {
+        return null;
+    }
+
     /**
      * @param warnings Warnings.
      */

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConfigurationEx.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConfigurationEx.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConfigurationEx.java
index eaf0997..78fb755 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConfigurationEx.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/dotnet/PlatformDotNetConfigurationEx.java
@@ -18,12 +18,16 @@
 package org.apache.ignite.internal.processors.platform.dotnet;
 
 import org.apache.ignite.internal.processors.platform.PlatformConfigurationEx;
+import org.apache.ignite.internal.processors.platform.cache.PlatformCacheExtension;
 import org.apache.ignite.internal.processors.platform.callback.PlatformCallbackGateway;
 import org.apache.ignite.internal.processors.platform.memory.PlatformMemoryManagerImpl;
 import org.apache.ignite.internal.processors.platform.utils.PlatformUtils;
+import org.apache.ignite.internal.processors.platform.websession.PlatformDotNetSessionCacheExtension;
 import org.apache.ignite.platform.dotnet.PlatformDotNetConfiguration;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
+import java.util.Collections;
 
 /**
  * Extended .Net configuration.
@@ -73,6 +77,11 @@ public class PlatformDotNetConfigurationEx extends PlatformDotNetConfiguration i
         return warnings;
     }
 
+    /** {@inheritDoc} */
+    @Nullable @Override public Collection<PlatformCacheExtension> cacheExtensions() {
+        return Collections.<PlatformCacheExtension>singleton(new PlatformDotNetSessionCacheExtension());
+    }
+
     /**
      * @param warnings Warnings.
      */

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionCacheExtension.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionCacheExtension.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionCacheExtension.java
new file mode 100644
index 0000000..aa5f69f
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionCacheExtension.java
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.platform.websession;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.binary.BinaryRawReaderEx;
+import org.apache.ignite.internal.binary.BinaryRawWriterEx;
+import org.apache.ignite.internal.processors.platform.cache.PlatformCache;
+import org.apache.ignite.internal.processors.platform.cache.PlatformCacheExtension;
+import org.apache.ignite.internal.processors.platform.memory.PlatformMemory;
+import org.apache.ignite.internal.processors.platform.utils.PlatformWriterClosure;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+import java.sql.Timestamp;
+import java.util.UUID;
+
+/**
+ * Custom entry processor invoker.
+ */
+public class PlatformDotNetSessionCacheExtension implements PlatformCacheExtension {
+    /** Extension ID. */
+    private static final int EXT_ID = 0;
+
+    /** Operation: session lock. */
+    private static final int OP_LOCK = 1;
+
+    /** Operation: session set/unlock. */
+    private static final int OP_SET_AND_UNLOCK = 2;
+
+    /** Operation: session get without lock. */
+    private static final int OP_GET = 3;
+
+    /** Operation: session put without lock. */
+    private static final int OP_PUT = 4;
+
+    /** Operation: session remove without lock. */
+    private static final int OP_REMOVE = 5;
+
+    /** {@inheritDoc} */
+    @Override public int id() {
+        return EXT_ID;
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override public long processInOutStreamLong(PlatformCache target, int type, BinaryRawReaderEx reader,
+        PlatformMemory mem) throws IgniteCheckedException {
+        switch (type) {
+            case OP_LOCK: {
+                String key = reader.readString();
+                UUID lockNodeId = reader.readUuid();
+                long lockId = reader.readLong();
+                Timestamp lockTime = reader.readTimestamp();
+
+                final PlatformDotNetSessionLockResult res = (PlatformDotNetSessionLockResult)
+                    target.rawCache().invoke(key, new PlatformDotNetSessionLockProcessor(lockNodeId, lockId, lockTime));
+
+                return target.writeResult(mem, res, new PlatformWriterClosure() {
+                    @Override public void write(BinaryRawWriterEx writer, Object val) {
+                        res.writeBinary(writer);
+                    }
+                });
+            }
+
+            case OP_SET_AND_UNLOCK: {
+                String key = reader.readString();
+
+                PlatformDotNetSessionSetAndUnlockProcessor proc;
+
+                if (reader.readBoolean()) {
+                    PlatformDotNetSessionData data = new PlatformDotNetSessionData();
+
+                    data.readBinary(reader);
+
+                    proc = new PlatformDotNetSessionSetAndUnlockProcessor(data);
+                }
+                else {
+                    UUID lockNodeId = reader.readUuid();
+                    long lockId = reader.readLong();
+
+                    proc = new PlatformDotNetSessionSetAndUnlockProcessor(lockNodeId, lockId);
+                }
+
+                target.rawCache().invoke(key, proc);
+
+                return target.writeResult(mem, null);
+            }
+
+            case OP_GET: {
+                String key = reader.readString();
+
+                final PlatformDotNetSessionData data = (PlatformDotNetSessionData)target.rawCache().get(key);
+
+                return target.writeResult(mem, data, new PlatformWriterClosure() {
+                    @Override public void write(BinaryRawWriterEx writer, Object val) {
+                        data.writeBinary(writer);
+                    }
+                });
+            }
+
+            case OP_PUT: {
+                String key = reader.readString();
+
+                PlatformDotNetSessionData data = new PlatformDotNetSessionData();
+
+                data.readBinary(reader);
+
+                target.rawCache().put(key, data);
+
+                return target.writeResult(mem, null);
+            }
+
+            case OP_REMOVE: {
+                String key = reader.readString();
+
+                target.rawCache().remove(key);
+
+                return target.writeResult(mem, null);
+            }
+        }
+
+        throw new IgniteCheckedException("Unsupported operation type: " + type);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(PlatformDotNetSessionCacheExtension.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionData.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionData.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionData.java
new file mode 100644
index 0000000..18dbab0
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionData.java
@@ -0,0 +1,260 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.platform.websession;
+
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinaryRawReader;
+import org.apache.ignite.binary.BinaryRawWriter;
+import org.apache.ignite.binary.BinaryReader;
+import org.apache.ignite.binary.BinaryWriter;
+import org.apache.ignite.binary.Binarylizable;
+import org.apache.ignite.internal.util.tostring.GridToStringExclude;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+import java.sql.Timestamp;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+
+/**
+ * Web session state data.
+ */
+@SuppressWarnings({"ReturnOfDateField", "AssignmentToDateFieldFromParameter"})
+public class PlatformDotNetSessionData implements Binarylizable {
+    /** Items. */
+    private Map<String, byte[]> items;
+
+    /** Static objects. */
+    @GridToStringExclude
+    private byte[] staticObjects;
+
+    /** Timeout. */
+    private int timeout;
+
+    /** Lock ID. */
+    private long lockId;
+
+    /** Lock node ID. */
+    private UUID lockNodeId;
+
+    /** Lock time. */
+    private Timestamp lockTime;
+
+    /**
+     * @return Items.
+     */
+    public Map<String, byte[]> items() {
+        return items;
+    }
+
+    /**
+     * @return Static objects.
+     */
+    public byte[] staticObjects() {
+        return staticObjects;
+    }
+
+    /**
+     * @return Timeout.
+     */
+    public int timeout() {
+        return timeout;
+    }
+
+    /**
+     * @return Lock ID.
+     */
+    public long lockId() {
+        return lockId;
+    }
+
+    /**
+     * @return Lock node ID.
+     */
+    public UUID lockNodeId() {
+        return lockNodeId;
+    }
+
+    /**
+     * @return Lock time.
+     */
+    public Timestamp lockTime() {
+        return lockTime;
+    }
+
+    /**
+     * @return {@code True} if locked.
+     */
+    public boolean isLocked() {
+        return lockTime != null;
+    }
+
+    /**
+     * Locks the session state data.
+     *
+     * @param lockNodeId Lock node ID.
+     * @param lockId Lock ID.
+     * @param lockTime Lock time.
+     *
+     * @return Unlocked data copy.
+     */
+    public PlatformDotNetSessionData lock(UUID lockNodeId, long lockId, Timestamp lockTime) {
+        assert !isLocked();
+
+        PlatformDotNetSessionData res = copyWithoutLockInfo();
+
+        res.lockId = lockId;
+        res.lockNodeId = lockNodeId;
+        res.lockTime = lockTime;
+
+        return res;
+    }
+
+    /**
+     * Unlocks the session state data.
+     *
+     * @param lockNodeId Lock node ID.
+     * @param lockId Lock ID.
+     *
+     * @return Unlocked data copy.
+     */
+    public PlatformDotNetSessionData unlock(UUID lockNodeId, long lockId) {
+        assert isLocked();
+
+        if (!this.lockNodeId.equals(lockNodeId))
+            throw new IllegalStateException("Can not unlock session data: lock node id check failed.");
+
+        if (this.lockId != lockId)
+            throw new IllegalStateException("Can not unlock session data: lock id check failed.");
+
+        return copyWithoutLockInfo();
+    }
+
+    /**
+     * Update session state and release the lock.
+     *
+     * @param lockNodeId Lock node ID.
+     * @param lockId Lock ID.
+     * @param items Items.
+     * @param isDiff Diff flag.
+     * @param staticObjects Static objects.
+     * @param timeout Timeout.
+     * @return Result.
+     */
+    public PlatformDotNetSessionData updateAndUnlock(UUID lockNodeId, long lockId, Map<String, byte[]> items,
+        boolean isDiff, byte[] staticObjects, int timeout) {
+        assert items != null;
+
+        PlatformDotNetSessionData res = unlock(lockNodeId, lockId);
+
+        if (!isDiff) {
+            // Not a diff: remove all
+            this.items.clear();
+        }
+
+        for (Map.Entry<String, byte[]> e : items.entrySet()) {
+            String key = e.getKey();
+            byte[] value = e.getValue();
+
+            if (value != null)
+                this.items.put(key, value);
+            else
+                this.items.remove(key);   // Null value indicates removed key.
+        }
+
+        res.staticObjects = staticObjects;
+        res.timeout = timeout;
+
+        return res;
+    }
+
+    /**
+     * Gets a copy of this instance with non-lock properties set.
+     *
+     * @return Copied state data.
+     */
+    private PlatformDotNetSessionData copyWithoutLockInfo() {
+        PlatformDotNetSessionData res = new PlatformDotNetSessionData();
+
+        res.staticObjects = staticObjects;
+        res.items = items;
+        res.timeout = timeout;
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
+        BinaryRawWriter raw = writer.rawWriter();
+
+        writeBinary(raw);
+    }
+
+    /**
+     * Writes to a binary writer.
+     *
+     * @param writer Binary writer.
+     */
+    public void writeBinary(BinaryRawWriter writer) {
+        writer.writeInt(items.size());
+
+        for (Map.Entry<String, byte[]> e : items.entrySet()) {
+            writer.writeString(e.getKey());
+            writer.writeByteArray(e.getValue());
+        }
+
+        writer.writeByteArray(staticObjects);
+
+        writer.writeInt(timeout);
+        writer.writeUuid(lockNodeId);
+        writer.writeLong(lockId);
+        writer.writeTimestamp(lockTime);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
+        BinaryRawReader raw = reader.rawReader();
+
+        readBinary(raw);
+    }
+
+    /**
+     * Reads from a binary reader.
+     *
+     * @param reader Reader.
+     */
+    public void readBinary(BinaryRawReader reader) {
+        items = new TreeMap<>();
+        int count = reader.readInt();
+
+        for (int i = 0; i < count; i++)
+            items.put(reader.readString(), reader.readByteArray());
+
+        staticObjects = reader.readByteArray();
+
+        timeout = reader.readInt();
+        lockNodeId = reader.readUuid();
+        lockId = reader.readLong();
+        lockTime = reader.readTimestamp();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(PlatformDotNetSessionData.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionLockProcessor.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionLockProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionLockProcessor.java
new file mode 100644
index 0000000..0e51448
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionLockProcessor.java
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.platform.websession;
+
+import org.apache.ignite.cache.CacheEntryProcessor;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+import javax.cache.processor.EntryProcessorException;
+import javax.cache.processor.MutableEntry;
+import java.sql.Timestamp;
+import java.util.UUID;
+
+/**
+ * Entry processor that locks web session data.
+ */
+@SuppressWarnings("AssignmentToDateFieldFromParameter")
+public class PlatformDotNetSessionLockProcessor implements CacheEntryProcessor<String, PlatformDotNetSessionData, Object> {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Lock node id. */
+    private UUID lockNodeId;
+
+    /** Lock id. */
+    private long lockId;
+
+    /** Lock time. */
+    private Timestamp lockTime;
+
+    /**
+     * Ctor.
+     *
+     * @param lockNodeId Lock node id.
+     * @param lockId Lock id.
+     * @param lockTime Lock time.
+     */
+    public PlatformDotNetSessionLockProcessor(UUID lockNodeId, long lockId, Timestamp lockTime) {
+        this.lockNodeId = lockNodeId;
+        this.lockId = lockId;
+        this.lockTime = lockTime;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object process(MutableEntry<String, PlatformDotNetSessionData> entry, Object... args)
+        throws EntryProcessorException {
+        if (!entry.exists())
+            return null;
+
+        PlatformDotNetSessionData data = entry.getValue();
+
+        assert data != null;
+
+        if (data.isLocked())
+            return new PlatformDotNetSessionLockResult(false, null, data.lockTime(), data.lockId());
+
+        // Not locked: lock and return result
+        data = data.lock(lockNodeId, lockId, lockTime);
+
+        // Apply.
+        entry.setValue(data);
+
+        return new PlatformDotNetSessionLockResult(true, data, null, data.lockId());
+    }
+
+    /** {@inheritDoc */
+    @Override public String toString() {
+        return S.toString(PlatformDotNetSessionLockProcessor.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionLockResult.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionLockResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionLockResult.java
new file mode 100644
index 0000000..cfa355c
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionLockResult.java
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.platform.websession;
+
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinaryRawReader;
+import org.apache.ignite.binary.BinaryRawWriter;
+import org.apache.ignite.binary.BinaryReader;
+import org.apache.ignite.binary.BinaryWriter;
+import org.apache.ignite.binary.Binarylizable;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+import java.sql.Timestamp;
+
+/**
+ * Result of the {@link PlatformDotNetSessionLockProcessor} execution.
+ */
+@SuppressWarnings({"AssignmentToDateFieldFromParameter", "ReturnOfDateField"})
+public class PlatformDotNetSessionLockResult implements Binarylizable {
+    /** Success flag. */
+    private boolean success;
+
+    /** Data. */
+    private PlatformDotNetSessionData data;
+
+    /** Lock time. */
+    private Timestamp lockTime;
+
+    /** Lock id. */
+    private long lockId;
+
+    /**
+     * Constructor.
+     *
+     * @param success Success flag.
+     * @param data Session data.
+     * @param lockTime Lock time.
+     */
+    public PlatformDotNetSessionLockResult(boolean success, PlatformDotNetSessionData data, Timestamp lockTime,
+        long lockId) {
+        assert success ^ (data == null);
+
+        this.success = success;
+        this.data = data;
+        this.lockTime = lockTime;
+        this.lockId = lockId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
+        BinaryRawWriter raw = writer.rawWriter();
+
+        writeBinary(raw);
+    }
+
+    /**
+     * Writes to a binary writer.
+     *
+     * @param writer Binary writer.
+     */
+    public void writeBinary(BinaryRawWriter writer) {
+        writer.writeBoolean(success);
+
+        if (success)
+            data.writeBinary(writer);
+
+        writer.writeTimestamp(lockTime);
+        writer.writeLong(lockId);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
+        BinaryRawReader raw = reader.rawReader();
+
+        success = raw.readBoolean();
+
+        if (success) {
+            data = new PlatformDotNetSessionData();
+
+            data.readBinary(raw);
+        }
+
+        lockTime = raw.readTimestamp();
+        lockId = raw.readLong();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(PlatformDotNetSessionLockResult.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionSetAndUnlockProcessor.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionSetAndUnlockProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionSetAndUnlockProcessor.java
new file mode 100644
index 0000000..9015c5c
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/websession/PlatformDotNetSessionSetAndUnlockProcessor.java
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.platform.websession;
+
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinaryRawReader;
+import org.apache.ignite.binary.BinaryRawWriter;
+import org.apache.ignite.binary.BinaryReader;
+import org.apache.ignite.binary.BinaryWriter;
+import org.apache.ignite.binary.Binarylizable;
+import org.apache.ignite.cache.CacheEntryProcessor;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+import javax.cache.processor.EntryProcessorException;
+import javax.cache.processor.MutableEntry;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+
+/**
+ * Processor to unlock and optionally update the session.
+ */
+public class PlatformDotNetSessionSetAndUnlockProcessor implements
+    CacheEntryProcessor<String, PlatformDotNetSessionData, Void>, Binarylizable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Lock node ID. */
+    private UUID lockNodeId;
+
+    /** Lock ID. */
+    private long lockId;
+
+    /** Update flag. */
+    private boolean update;
+
+    /** Data. */
+    private Map<String, byte[]> items;
+
+    /** Whether items collection represents a diff. */
+    private boolean isDiff;
+
+    /** Static data. */
+    private byte[] staticData;
+
+    /** Timeout. */
+    private int timeout;
+
+    /**
+     * Constructor for unlock.
+     *
+     * @param lockNodeId Lock node ID.
+     * @param lockId Lock ID.
+     */
+    public PlatformDotNetSessionSetAndUnlockProcessor(UUID lockNodeId, long lockId) {
+        this(lockNodeId, lockId, false, null, false, null, 0);
+    }
+
+    /**
+     * Constructor for unlock/update.
+     *
+     * @param data Data.
+     */
+    public PlatformDotNetSessionSetAndUnlockProcessor(PlatformDotNetSessionData data) {
+        this(data.lockNodeId(), data.lockId(), true, data.items(), true, data.staticObjects(), data.timeout());
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param lockNodeId Lock node ID.
+     * @param lockId Lock ID.
+     * @param update Whether to perform update.
+     * @param items Items.
+     * @param isDiff Whether items is a diff.
+     * @param staticData Static data.
+     * @param timeout Timeout.
+     */
+    public PlatformDotNetSessionSetAndUnlockProcessor(UUID lockNodeId, long lockId, boolean update,
+        Map<String, byte[]> items, boolean isDiff, byte[] staticData, int timeout) {
+        this.lockNodeId = lockNodeId;
+        this.lockId = lockId;
+        this.update = update;
+        this.items = items;
+        this.isDiff = isDiff;
+        this.staticData = staticData;
+        this.timeout = timeout;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Void process(MutableEntry<String, PlatformDotNetSessionData> entry, Object... args)
+        throws EntryProcessorException {
+        assert entry.exists();
+
+        PlatformDotNetSessionData data = entry.getValue();
+
+        assert data != null;
+
+        // Unlock and update.
+        data = update
+            ? data.updateAndUnlock(lockNodeId, lockId, items, isDiff, staticData, timeout)
+            : data.unlock(lockNodeId, lockId);
+
+        // Apply.
+        entry.setValue(data);
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
+        BinaryRawWriter raw = writer.rawWriter();
+
+        raw.writeUuid(lockNodeId);
+        raw.writeLong(lockId);
+        raw.writeBoolean(update);
+
+        if (update) {
+            raw.writeBoolean(isDiff);
+            raw.writeByteArray(staticData);
+            raw.writeInt(timeout);
+
+            if (items != null) {
+                raw.writeInt(items.size());
+
+                for (Map.Entry<String, byte[]> e : items.entrySet()) {
+                    raw.writeString(e.getKey());
+                    raw.writeByteArray(e.getValue());
+                }
+            }
+            else
+                raw.writeInt(-1);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
+        BinaryRawReader raw = reader.rawReader();
+
+        lockNodeId = raw.readUuid();
+        lockId = raw.readLong();
+        update = raw.readBoolean();
+
+        if (update) {
+            isDiff = raw.readBoolean();
+            staticData = raw.readByteArray();
+            timeout = raw.readInt();
+
+            int cnt = raw.readInt();
+
+            if (cnt >= 0) {
+                items = new TreeMap<>();
+
+                for (int i = 0; i < cnt; i++)
+                    items.put(raw.readString(), raw.readByteArray());
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(PlatformDotNetSessionSetAndUnlockProcessor.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Apache.Ignite.AspNet.Tests.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Apache.Ignite.AspNet.Tests.csproj b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Apache.Ignite.AspNet.Tests.csproj
new file mode 100644
index 0000000..aed74db
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Apache.Ignite.AspNet.Tests.csproj
@@ -0,0 +1,77 @@
+\ufeff<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{18EA4C71-A11D-4AB1-8042-418F7559D84F}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Apache.Ignite.AspNet.Tests</RootNamespace>
+    <AssemblyName>Apache.Ignite.AspNet.Tests</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <RunCodeAnalysis>true</RunCodeAnalysis>
+    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <SignAssembly>true</SignAssembly>
+  </PropertyGroup>
+  <PropertyGroup>
+    <AssemblyOriginatorKeyFile>Apache.Ignite.AspNet.Tests.snk</AssemblyOriginatorKeyFile>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="nunit.framework">
+      <HintPath>..\packages\NUnit.Runners.2.6.3\tools\nunit.framework.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Configuration" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Web" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="ExpiryCacheHolderTest.cs" />
+    <Compile Include="IgniteOutputCacheProviderTest.cs" />
+    <Compile Include="IgniteSessionStateItemCollectionTest.cs" />
+    <Compile Include="IgniteSessionStateStoreDataTest.cs" />
+    <Compile Include="IgniteSessionStateStoreProviderTest.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Apache.Ignite.AspNet.Tests.snk" />
+    <None Include="App.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Apache.Ignite.AspNet\Apache.Ignite.AspNet.csproj">
+      <Project>{13EA96FC-CC83-4164-A7C0-4F30ED797460}</Project>
+      <Name>Apache.Ignite.AspNet</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Apache.Ignite.Core.Tests\Apache.Ignite.Core.Tests.csproj">
+      <Project>{6A62F66C-DA5B-4FBB-8CE7-A95F740FDC7A}</Project>
+      <Name>Apache.Ignite.Core.Tests</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Apache.Ignite.Core\Apache.Ignite.Core.csproj">
+      <Project>{4CD2F726-7E2B-46C4-A5BA-057BB82EECB6}</Project>
+      <Name>Apache.Ignite.Core</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Apache.Ignite.AspNet.Tests.snk
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Apache.Ignite.AspNet.Tests.snk b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Apache.Ignite.AspNet.Tests.snk
new file mode 100644
index 0000000..799e742
Binary files /dev/null and b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/Apache.Ignite.AspNet.Tests.snk differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/App.config
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/App.config b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/App.config
new file mode 100644
index 0000000..86ee3d4
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/App.config
@@ -0,0 +1,72 @@
+\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.
+-->
+
+<configuration>
+    <configSections>
+        <section name="igniteConfiguration" type="Apache.Ignite.Core.IgniteConfigurationSection, Apache.Ignite.Core" />
+        <section name="igniteConfiguration2" type="Apache.Ignite.Core.IgniteConfigurationSection, Apache.Ignite.Core" />
+        <section name="igniteConfiguration3" type="Apache.Ignite.Core.IgniteConfigurationSection, Apache.Ignite.Core" />
+    </configSections>
+
+    <runtime>
+        <gcServer enabled="true"/>
+    </runtime>
+
+    <igniteConfiguration xmlns="http://ignite.apache.org/schema/dotnet/IgniteConfigurationSection" gridName="myGrid1">
+        <discoverySpi type="TcpDiscoverySpi">
+            <ipFinder type="TcpDiscoveryStaticIpFinder">
+                <endpoints>
+                    <string>127.0.0.1:47500</string>
+                </endpoints>
+            </ipFinder>
+        </discoverySpi>
+
+        <cacheConfiguration>
+            <cacheConfiguration name="cacheName" />
+        </cacheConfiguration>
+    </igniteConfiguration>
+
+    <igniteConfiguration2 gridName="myGrid2" localhost="127.0.0.1">
+        <discoverySpi type="TcpDiscoverySpi">
+            <ipFinder type="TcpDiscoveryStaticIpFinder">
+                <endpoints>
+                    <string>127.0.0.1:47500</string>
+                </endpoints>
+            </ipFinder>
+        </discoverySpi>
+
+        <cacheConfiguration>
+            <cacheConfiguration name="cacheName2" />
+        </cacheConfiguration>
+    </igniteConfiguration2>
+
+    <igniteConfiguration3 gridName="myGrid3" localhost="127.0.0.1">
+        <discoverySpi type="TcpDiscoverySpi">
+            <ipFinder type="TcpDiscoveryStaticIpFinder">
+                <endpoints>
+                    <string>127.0.0.1:47500</string>
+                </endpoints>
+            </ipFinder>
+        </discoverySpi>
+
+        <cacheConfiguration>
+            <cacheConfiguration name="cacheName3" atomicityMode="Transactional" />
+        </cacheConfiguration>
+    </igniteConfiguration3>
+</configuration>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/799f1909/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/ExpiryCacheHolderTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/ExpiryCacheHolderTest.cs b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/ExpiryCacheHolderTest.cs
new file mode 100644
index 0000000..c12fe93
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.AspNet.Tests/ExpiryCacheHolderTest.cs
@@ -0,0 +1,492 @@
+\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.
+ */
+
+// ReSharper disable UnusedAutoPropertyAccessor.Local
+namespace Apache.Ignite.AspNet.Tests
+{
+    using System;
+    using System.Collections;
+    using System.Collections.Generic;
+    using System.Threading.Tasks;
+    using Apache.Ignite.AspNet.Impl;
+    using Apache.Ignite.Core;
+    using Apache.Ignite.Core.Cache;
+    using Apache.Ignite.Core.Cache.Configuration;
+    using Apache.Ignite.Core.Cache.Expiry;
+    using Apache.Ignite.Core.Cache.Query;
+    using Apache.Ignite.Core.Cache.Query.Continuous;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests for <see cref="ExpiryCacheHolder{TK,TV}"/>.
+    /// </summary>
+    public class ExpiryCacheHolderTest
+    {
+        /// <summary>
+        /// Tests the expiry policy.
+        /// </summary>
+        [Test]
+        public void TestExpiryPolicy()
+        {
+            var cache = new CacheEx();
+
+            Assert.IsNull(cache.ExpiryPolicy);
+
+            var holder = new ExpiryCacheHolder<int, int>(cache);
+
+            // Check same cache for same expiry.
+            var cache1 = (CacheEx) holder.GetCacheWithExpiry(15);
+
+            CheckExpiry(TimeSpan.FromSeconds(15), cache1);
+            Assert.AreNotSame(cache, cache1);
+            Assert.AreSame(cache1, holder.GetCacheWithExpiry(15));
+
+            // Check rounding.
+            var cache2 = (CacheEx) holder.GetCacheWithExpiry(DateTime.UtcNow.AddSeconds(15.1));
+            Assert.AreSame(cache1, cache2);
+
+            // Check no expiration.
+            var cache3 = (CacheEx) holder.GetCacheWithExpiry(DateTime.MaxValue);
+            Assert.AreSame(cache, cache3);
+        }
+
+        /// <summary>
+        /// Checks the expiry.
+        /// </summary>
+        private static void CheckExpiry(TimeSpan timeSpan, CacheEx cache)
+        {
+            Assert.AreEqual(timeSpan, cache.ExpiryPolicy.GetExpiryForCreate());
+            Assert.IsNull(cache.ExpiryPolicy.GetExpiryForUpdate());
+            Assert.IsNull(cache.ExpiryPolicy.GetExpiryForAccess());
+        }
+
+        /// <summary>
+        /// Test cache implementation.
+        /// </summary>
+        private class CacheEx : ICache<int, int>
+        {
+            public IExpiryPolicy ExpiryPolicy { get; set; }
+
+            public IEnumerator<ICacheEntry<int, int>> GetEnumerator()
+            {
+                throw new NotImplementedException();
+            }
+
+            IEnumerator IEnumerable.GetEnumerator()
+            {
+                return GetEnumerator();
+            }
+
+            public string Name { get; private set; }
+
+            public IIgnite Ignite { get; private set; }
+
+            public CacheConfiguration GetConfiguration()
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool IsEmpty()
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool IsKeepBinary { get; private set; }
+
+            public ICache<int, int> WithSkipStore()
+            {
+                throw new NotImplementedException();
+            }
+
+            public ICache<int, int> WithExpiryPolicy(IExpiryPolicy plc)
+            {
+                return new CacheEx {ExpiryPolicy = plc};
+            }
+
+            public ICache<TK1, TV1> WithKeepBinary<TK1, TV1>()
+            {
+                throw new NotImplementedException();
+            }
+
+            public void LoadCache(ICacheEntryFilter<int, int> p, params object[] args)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task LoadCacheAsync(ICacheEntryFilter<int, int> p, params object[] args)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void LocalLoadCache(ICacheEntryFilter<int, int> p, params object[] args)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task LocalLoadCacheAsync(ICacheEntryFilter<int, int> p, params object[] args)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void LoadAll(IEnumerable<int> keys, bool replaceExistingValues)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task LoadAllAsync(IEnumerable<int> keys, bool replaceExistingValues)
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool ContainsKey(int key)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<bool> ContainsKeyAsync(int key)
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool ContainsKeys(IEnumerable<int> keys)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<bool> ContainsKeysAsync(IEnumerable<int> keys)
+            {
+                throw new NotImplementedException();
+            }
+
+            public int LocalPeek(int key, params CachePeekMode[] modes)
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool TryLocalPeek(int key, out int value, params CachePeekMode[] modes)
+            {
+                throw new NotImplementedException();
+            }
+
+            public int this[int key]
+            {
+                get { throw new NotImplementedException(); }
+                set { throw new NotImplementedException(); }
+            }
+
+            public int Get(int key)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<int> GetAsync(int key)
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool TryGet(int key, out int value)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<CacheResult<int>> TryGetAsync(int key)
+            {
+                throw new NotImplementedException();
+            }
+
+            public IDictionary<int, int> GetAll(IEnumerable<int> keys)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<IDictionary<int, int>> GetAllAsync(IEnumerable<int> keys)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void Put(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task PutAsync(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public CacheResult<int> GetAndPut(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<CacheResult<int>> GetAndPutAsync(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public CacheResult<int> GetAndReplace(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<CacheResult<int>> GetAndReplaceAsync(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public CacheResult<int> GetAndRemove(int key)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<CacheResult<int>> GetAndRemoveAsync(int key)
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool PutIfAbsent(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<bool> PutIfAbsentAsync(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public CacheResult<int> GetAndPutIfAbsent(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<CacheResult<int>> GetAndPutIfAbsentAsync(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool Replace(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<bool> ReplaceAsync(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool Replace(int key, int oldVal, int newVal)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<bool> ReplaceAsync(int key, int oldVal, int newVal)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void PutAll(IDictionary<int, int> vals)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task PutAllAsync(IDictionary<int, int> vals)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void LocalEvict(IEnumerable<int> keys)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void Clear()
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task ClearAsync()
+            {
+                throw new NotImplementedException();
+            }
+
+            public void Clear(int key)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task ClearAsync(int key)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void ClearAll(IEnumerable<int> keys)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task ClearAllAsync(IEnumerable<int> keys)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void LocalClear(int key)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void LocalClearAll(IEnumerable<int> keys)
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool Remove(int key)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<bool> RemoveAsync(int key)
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool Remove(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<bool> RemoveAsync(int key, int val)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void RemoveAll(IEnumerable<int> keys)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task RemoveAllAsync(IEnumerable<int> keys)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void RemoveAll()
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task RemoveAllAsync()
+            {
+                throw new NotImplementedException();
+            }
+
+            public int GetLocalSize(params CachePeekMode[] modes)
+            {
+                throw new NotImplementedException();
+            }
+
+            public int GetSize(params CachePeekMode[] modes)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<int> GetSizeAsync(params CachePeekMode[] modes)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void LocalPromote(IEnumerable<int> keys)
+            {
+                throw new NotImplementedException();
+            }
+
+            public IQueryCursor<ICacheEntry<int, int>> Query(QueryBase qry)
+            {
+                throw new NotImplementedException();
+            }
+
+            public IQueryCursor<IList> QueryFields(SqlFieldsQuery qry)
+            {
+                throw new NotImplementedException();
+            }
+
+            public IContinuousQueryHandle QueryContinuous(ContinuousQuery<int, int> qry)
+            {
+                throw new NotImplementedException();
+            }
+
+            public IContinuousQueryHandle<ICacheEntry<int, int>> QueryContinuous(ContinuousQuery<int, int> qry, QueryBase initialQry)
+            {
+                throw new NotImplementedException();
+            }
+
+            public IEnumerable<ICacheEntry<int, int>> GetLocalEntries(params CachePeekMode[] peekModes)
+            {
+                throw new NotImplementedException();
+            }
+
+            public TRes Invoke<TArg, TRes>(int key, ICacheEntryProcessor<int, int, TArg, TRes> processor, TArg arg)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<TRes> InvokeAsync<TArg, TRes>(int key, ICacheEntryProcessor<int, int, TArg, TRes> processor, TArg arg)
+            {
+                throw new NotImplementedException();
+            }
+
+            public IDictionary<int, ICacheEntryProcessorResult<TRes>> InvokeAll<TArg, TRes>(IEnumerable<int> keys, ICacheEntryProcessor<int, int, TArg, TRes> processor, TArg arg)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task<IDictionary<int, ICacheEntryProcessorResult<TRes>>> InvokeAllAsync<TArg, TRes>(IEnumerable<int> keys, ICacheEntryProcessor<int, int, TArg, TRes> processor, TArg arg)
+            {
+                throw new NotImplementedException();
+            }
+
+            public ICacheLock Lock(int key)
+            {
+                throw new NotImplementedException();
+            }
+
+            public ICacheLock LockAll(IEnumerable<int> keys)
+            {
+                throw new NotImplementedException();
+            }
+
+            public bool IsLocalLocked(int key, bool byCurrentThread)
+            {
+                throw new NotImplementedException();
+            }
+
+            public ICacheMetrics GetMetrics()
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task Rebalance()
+            {
+                throw new NotImplementedException();
+            }
+
+            public ICache<int, int> WithNoRetries()
+            {
+                throw new NotImplementedException();
+            }
+        }
+    }
+}