You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2021/11/22 07:55:43 UTC

[ignite] branch master updated: IGNITE-15795 .NET: Add ServiceCallContext (#9526)

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

ptupitsyn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new cca909c  IGNITE-15795 .NET: Add ServiceCallContext (#9526)
cca909c is described below

commit cca909ca92965aa12dca5139ec2803df4ffbc4d4
Author: Pavel Pereslegin <xx...@gmail.com>
AuthorDate: Mon Nov 22 10:55:24 2021 +0300

    IGNITE-15795 .NET: Add ServiceCallContext (#9526)
    
    ServiceCallContext provides a way to pass additional data implicitly with all service method calls issued by the current service proxy.
---
 .../client/service/ClientServiceInvokeRequest.java |   2 +-
 .../platform/services/PlatformAbstractService.java |  19 ++--
 .../platform/services/PlatformService.java         |  25 +++--
 .../platform/services/PlatformServices.java        |  13 ++-
 .../processors/service/GridServiceProxy.java       |  11 ++-
 .../processors/service/ServiceCallContextImpl.java |  23 ++---
 .../ignite/services/ServiceCallContextBuilder.java |  12 +--
 .../platform/AbstractPlatformServiceCallTask.java  |   9 +-
 .../ignite/platform/PlatformDeployServiceTask.java |  10 ++
 .../ignite/platform/PlatformServiceCallTask.java   |   6 ++
 .../platform/PlatformServiceCallThinTask.java      |   4 +
 .../Services/CallPlatformServiceTest.cs            |  13 ++-
 .../Services/IJavaService.cs                       |   3 +
 .../Services/JavaServiceDynamicProxy.cs            |   6 ++
 .../Services/PlatformTestService.cs                |  10 ++
 .../Services/ServiceProxyTest.cs                   |   5 +-
 .../Services/ServicesAsyncWrapper.cs               |  13 +++
 .../Services/ServicesTest.cs                       | 107 +++++++++++++++++++--
 .../Common/IgniteExperimentalAttribute.cs          |   4 +-
 .../Impl/Services/ServiceCallContext.cs            |  64 ++++++++++++
 .../Impl/Services/ServiceContext.cs                |  18 ++++
 .../Impl/Services/ServiceProxySerializer.cs        |  12 ++-
 .../Apache.Ignite.Core/Impl/Services/Services.cs   |  36 ++++---
 .../Impl/Unmanaged/UnmanagedCallbacks.cs           |  23 ++++-
 .../Services/IServiceCallContext.cs                |  78 +++++++++++++++
 .../Apache.Ignite.Core/Services/IServiceContext.cs |   9 ++
 .../Apache.Ignite.Core/Services/IServices.cs       |  31 ++++++
 .../Services/ServiceCallContextBuilder.cs          |  82 ++++++++++++++++
 28 files changed, 568 insertions(+), 80 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/service/ClientServiceInvokeRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/service/ClientServiceInvokeRequest.java
index acf0b80..7cace9c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/service/ClientServiceInvokeRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/service/ClientServiceInvokeRequest.java
@@ -154,7 +154,7 @@ public class ClientServiceInvokeRequest extends ClientRequest {
                 // Never deserialize platform service arguments and result: may contain platform-only types.
                 PlatformService proxy = services.serviceProxy(name, PlatformService.class, false, timeout);
 
-                res = proxy.invokeMethod(methodName, keepBinary(), false, args);
+                res = proxy.invokeMethod(methodName, keepBinary(), false, args, null);
             }
             else {
                 // Deserialize Java service arguments when not in keepBinary mode.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformAbstractService.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformAbstractService.java
index 59d87cb..e9eae0b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformAbstractService.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformAbstractService.java
@@ -21,6 +21,7 @@ import java.io.Externalizable;
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
+import java.util.Map;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.binary.BinaryRawReaderEx;
@@ -33,6 +34,7 @@ import org.apache.ignite.internal.processors.platform.utils.PlatformUtils;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.resources.IgniteInstanceResource;
 import org.apache.ignite.services.ServiceContext;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Base platform service implementation.
@@ -180,14 +182,13 @@ public abstract class PlatformAbstractService implements PlatformService, Extern
     }
 
     /** {@inheritDoc} */
-    @Override public Object invokeMethod(String mthdName, boolean srvKeepBinary, Object[] args)
-            throws IgniteCheckedException {
-        return invokeMethod(mthdName, srvKeepBinary, false, args);
-    }
-
-    /** {@inheritDoc} */
-    @Override public Object invokeMethod(String mthdName, boolean srvKeepBinary, boolean deserializeResult, Object[] args)
-        throws IgniteCheckedException {
+    @Override public Object invokeMethod(
+        String mthdName,
+        boolean srvKeepBinary,
+        boolean deserializeResult,
+        @Nullable Object[] args,
+        @Nullable Map<String, Object> callAttrs
+    ) throws IgniteCheckedException {
         assert ptr != 0;
         assert platformCtx != null;
 
@@ -209,6 +210,8 @@ public abstract class PlatformAbstractService implements PlatformService, Extern
                     writer.writeObjectDetached(arg);
             }
 
+            writer.writeMap(callAttrs);
+
             out.synchronize();
 
             platformCtx.gateway().serviceInvokeMethod(mem.pointer());
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformService.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformService.java
index de0b142..68f1716 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformService.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformService.java
@@ -17,8 +17,10 @@
 
 package org.apache.ignite.internal.processors.platform.services;
 
+import java.util.Map;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.services.Service;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Base class for all platform services.
@@ -29,24 +31,19 @@ public interface PlatformService extends Service {
      *
      * @param mthdName Method name.
      * @param srvKeepBinary Server keep binary flag.
-     * @param args Arguments.
-     * @return Resulting data.
-     * @throws org.apache.ignite.IgniteCheckedException If failed.
-     */
-    public Object invokeMethod(String mthdName, boolean srvKeepBinary, Object[] args) throws IgniteCheckedException;
-
-    /**
-     * Invokes native service method.
-     *
-     * @param mthdName Method name.
-     * @param srvKeepBinary Server keep binary flag.
-     * @param args Arguments.
      * @param deserializeResult If {@code true}, call service in cross-platform compatible manner.
+     * @param args Arguments.
+     * @param callAttrs Service call context attributes.
      * @return Resulting data.
      * @throws org.apache.ignite.IgniteCheckedException If failed.
      */
-    public Object invokeMethod(String mthdName, boolean srvKeepBinary, boolean deserializeResult, Object[] args)
-            throws IgniteCheckedException;
+    public Object invokeMethod(
+        String mthdName,
+        boolean srvKeepBinary,
+        boolean deserializeResult,
+        @Nullable Object[] args,
+        @Nullable Map<String, Object> callAttrs
+    ) throws IgniteCheckedException;
 
     /**
      * Gets native pointer.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java
index 8e62494..864075c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java
@@ -41,6 +41,7 @@ import org.apache.ignite.internal.processors.platform.utils.PlatformUtils;
 import org.apache.ignite.internal.processors.platform.utils.PlatformWriterBiClosure;
 import org.apache.ignite.internal.processors.platform.utils.PlatformWriterClosure;
 import org.apache.ignite.internal.processors.service.GridServiceProxy;
+import org.apache.ignite.internal.processors.service.ServiceCallContextImpl;
 import org.apache.ignite.internal.util.typedef.T3;
 import org.apache.ignite.lang.IgniteFuture;
 import org.apache.ignite.lang.IgnitePredicate;
@@ -285,8 +286,10 @@ public class PlatformServices extends PlatformAbstractTarget {
                 else
                     args = null;
 
+                Map<String, Object> callAttrs = reader.readMap();
+
                 try {
-                    Object result = svc.invoke(mthdName, srvKeepBinary, args);
+                    Object result = svc.invoke(mthdName, srvKeepBinary, args, callAttrs);
 
                     PlatformUtils.writeInvocationResult(writer, result, null);
                 }
@@ -605,13 +608,14 @@ public class PlatformServices extends PlatformAbstractTarget {
          * @param mthdName Method name.
          * @param srvKeepBinary Binary flag.
          * @param args Args.
+         * @param callAttrs Service call context attributes.
          * @return Invocation result.
          * @throws IgniteCheckedException On error.
          * @throws NoSuchMethodException On error.
          */
-        public Object invoke(String mthdName, boolean srvKeepBinary, Object[] args) throws Throwable {
+        public Object invoke(String mthdName, boolean srvKeepBinary, Object[] args, Map<String, Object> callAttrs) throws Throwable {
             if (isPlatformService())
-                return ((PlatformService)proxy).invokeMethod(mthdName, srvKeepBinary, args);
+                return ((PlatformService)proxy).invokeMethod(mthdName, srvKeepBinary, false, args, callAttrs);
             else {
                 assert proxy instanceof GridServiceProxy;
 
@@ -622,7 +626,8 @@ public class PlatformServices extends PlatformAbstractTarget {
                 Method mtd = getMethod(serviceClass, mthdName, args);
                 convertArrayArgs(args, mtd);
 
-                return ((GridServiceProxy)proxy).invokeMethod(mtd, args, null);
+                return ((GridServiceProxy)proxy)
+                    .invokeMethod(mtd, args, callAttrs == null ? null : new ServiceCallContextImpl(callAttrs));
             }
         }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java
index e101b6c..fba2f41 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java
@@ -74,7 +74,7 @@ public class GridServiceProxy<T> implements Serializable {
     static {
         try {
             PLATFORM_SERVICE_INVOKE_METHOD = PlatformService.class.getMethod("invokeMethod", String.class,
-                    boolean.class, Object[].class);
+                    boolean.class, boolean.class, Object[].class, Map.class);
         }
         catch (NoSuchMethodException e) {
             throw new ExceptionInInitializerError("'invokeMethod' is not defined in " + PlatformService.class.getName());
@@ -287,8 +287,11 @@ public class GridServiceProxy<T> implements Serializable {
         Object[] args,
         @Nullable ServiceCallContext callCtx
     ) throws Exception {
-        if (svc instanceof PlatformService && !PLATFORM_SERVICE_INVOKE_METHOD.equals(mtd))
-            return ((PlatformService)svc).invokeMethod(methodName(mtd), false, true, args);
+        if (svc instanceof PlatformService && !PLATFORM_SERVICE_INVOKE_METHOD.equals(mtd)) {
+            Map<String, Object> callAttrs = callCtx == null ? null : ((ServiceCallContextImpl)callCtx).values();
+
+            return ((PlatformService)svc).invokeMethod(methodName(mtd), false, true, args, callAttrs);
+        }
         else
             return callServiceMethod(svc, mtd, args, callCtx);
     }
@@ -531,7 +534,7 @@ public class GridServiceProxy<T> implements Serializable {
         /** */
         private Object callPlatformService(PlatformService srv) {
             try {
-                return srv.invokeMethod(mtdName, false, true, args);
+                return srv.invokeMethod(mtdName, false, true, args, callCtx != null ? ((ServiceCallContextImpl)callCtx).values() : null);
             }
             catch (PlatformNativeException ne) {
                 throw new ServiceProxyException(U.convertException(ne));
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceCallContextImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceCallContextImpl.java
index aceedca..ab53c12b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceCallContextImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceCallContextImpl.java
@@ -20,7 +20,6 @@ package org.apache.ignite.internal.processors.service;
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
-import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
 import org.apache.ignite.internal.util.typedef.internal.U;
@@ -34,7 +33,7 @@ public class ServiceCallContextImpl implements ServiceCallContext {
     private static final long serialVersionUID = 0L;
 
     /** Service call context attributes. */
-    private Map<String, byte[]> attrs;
+    private Map<String, Object> attrs;
 
     /**
      * Default contructor.
@@ -46,23 +45,18 @@ public class ServiceCallContextImpl implements ServiceCallContext {
     /**
      * @param attrs Service call context attributes.
      */
-    public ServiceCallContextImpl(Map<String, byte[]> attrs) {
-        this.attrs = new HashMap<>(attrs);
+    public ServiceCallContextImpl(Map<String, Object> attrs) {
+        this.attrs = attrs;
     }
 
     /** {@inheritDoc} */
     @Override public String attribute(String name) {
-        byte[] bytes = attrs.get(name);
-
-        if (bytes == null)
-            return null;
-
-        return new String(bytes, StandardCharsets.UTF_8);
+        return (String)attrs.get(name);
     }
 
     /** {@inheritDoc} */
     @Override public byte[] binaryAttribute(String name) {
-        return attrs.get(name);
+        return (byte[])attrs.get(name);
     }
 
     /** {@inheritDoc} */
@@ -74,4 +68,11 @@ public class ServiceCallContextImpl implements ServiceCallContext {
     @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
         attrs = U.readMap(in);
     }
+
+    /**
+     * @return Service call context attributes.
+     */
+    Map<String, Object> values() {
+        return attrs;
+    }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/services/ServiceCallContextBuilder.java b/modules/core/src/main/java/org/apache/ignite/services/ServiceCallContextBuilder.java
index bbb221a..9ba40df 100644
--- a/modules/core/src/main/java/org/apache/ignite/services/ServiceCallContextBuilder.java
+++ b/modules/core/src/main/java/org/apache/ignite/services/ServiceCallContextBuilder.java
@@ -17,8 +17,6 @@
 
 package org.apache.ignite.services;
 
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import org.apache.ignite.internal.processors.service.ServiceCallContextImpl;
@@ -31,7 +29,7 @@ import org.apache.ignite.lang.IgniteExperimental;
 @IgniteExperimental
 public class ServiceCallContextBuilder {
     /** Service call context attributes. */
-    private final Map<String, byte[]> attrs = new HashMap<>();
+    private final Map<String, Object> attrs = new HashMap<>();
 
     /**
      * Put string attribute.
@@ -44,13 +42,15 @@ public class ServiceCallContextBuilder {
         A.notNullOrEmpty(name, "name");
         A.notNull(value, "value");
 
-        attrs.put(name, value.getBytes(StandardCharsets.UTF_8));
+        attrs.put(name, value);
 
         return this;
     }
 
     /**
      * Put binary attribute.
+     * <p>
+     * <b>Note:</b> it is recommended to pass a copy of the array if the original can be changed later.
      *
      * @param name Attribute name.
      * @param value Attribute value.
@@ -60,7 +60,7 @@ public class ServiceCallContextBuilder {
         A.notNullOrEmpty(name, "name");
         A.notNull(value, "value");
 
-        attrs.put(name, Arrays.copyOf(value, value.length));
+        attrs.put(name, value);
 
         return this;
     }
@@ -72,6 +72,6 @@ public class ServiceCallContextBuilder {
         if (attrs.isEmpty())
             throw new IllegalStateException("Cannot create an empty context.");
 
-        return new ServiceCallContextImpl(attrs);
+        return new ServiceCallContextImpl(new HashMap<>(attrs));
     }
 }
diff --git a/modules/core/src/test/java/org/apache/ignite/platform/AbstractPlatformServiceCallTask.java b/modules/core/src/test/java/org/apache/ignite/platform/AbstractPlatformServiceCallTask.java
index bb5239b..ce39347 100644
--- a/modules/core/src/test/java/org/apache/ignite/platform/AbstractPlatformServiceCallTask.java
+++ b/modules/core/src/test/java/org/apache/ignite/platform/AbstractPlatformServiceCallTask.java
@@ -42,6 +42,7 @@ import org.apache.ignite.configuration.ClientConfiguration;
 import org.apache.ignite.internal.processors.odbc.ClientListenerProcessor;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.resources.IgniteInstanceResource;
+import org.apache.ignite.services.ServiceCallContext;
 import org.apache.ignite.services.ServiceDescriptor;
 import org.jetbrains.annotations.Nullable;
 
@@ -133,7 +134,9 @@ public abstract class AbstractPlatformServiceCallTask extends ComputeTaskAdapter
          * Gets service proxy.
          */
         TestPlatformService serviceProxy() {
-            return ignite.services().serviceProxy(srvcName, TestPlatformService.class, false);
+            ServiceCallContext callCtx = ServiceCallContext.builder().put("attr", "value").build();
+
+            return ignite.services().serviceProxy(srvcName, TestPlatformService.class, false, callCtx, 0);
         }
 
         /**
@@ -209,6 +212,10 @@ public abstract class AbstractPlatformServiceCallTask extends ComputeTaskAdapter
         /** */
         @PlatformServiceMethod("AddOne")
         BinarizableTestValue addOne(BinarizableTestValue val);
+
+        /** */
+        @PlatformServiceMethod("contextAttribute")
+        String contextAttribute(String name);
     }
 
     /** */
diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java
index bdb4823..dfeea31 100644
--- a/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java
+++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java
@@ -123,6 +123,9 @@ public class PlatformDeployServiceTask extends ComputeTaskAdapter<String, Object
         /** */
         private boolean isExecuted;
 
+        /** */
+        private ServiceContext svcCtx;
+
         /** {@inheritDoc} */
         @Override public void cancel(ServiceContext ctx) {
             isCancelled = true;
@@ -130,6 +133,8 @@ public class PlatformDeployServiceTask extends ComputeTaskAdapter<String, Object
 
         /** {@inheritDoc} */
         @Override public void init(ServiceContext ctx) throws Exception {
+            svcCtx = ctx;
+
             isInitialized = true;
         }
 
@@ -643,6 +648,11 @@ public class PlatformDeployServiceTask extends ComputeTaskAdapter<String, Object
                 throw new IgniteException(e);
             }
         }
+
+        /** */
+        public Object contextAttribute(String name) {
+            return svcCtx.currentCallContext().attribute(name);
+        }
     }
 
     /** */
diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallTask.java
index 97c0d54..43f8201 100644
--- a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallTask.java
+++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallTask.java
@@ -49,6 +49,7 @@ public class PlatformServiceCallTask extends AbstractPlatformServiceCallTask {
             checkUuidProp(srv);
             checkObjectProp(srv);
             checkErrorMethod(srv);
+            checkContextAttribute(srv);
         }
 
         /** */
@@ -79,5 +80,10 @@ public class PlatformServiceCallTask extends AbstractPlatformServiceCallTask {
 
             assertTrue(nativeEx.toString().contains("Failed method"));
         }
+
+        /** */
+        protected void checkContextAttribute(TestPlatformService srv) {
+            assertEquals("value", srv.contextAttribute("attr"));
+        }
     }
 }
diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallThinTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallThinTask.java
index a1c840f..efe8841 100644
--- a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallThinTask.java
+++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallThinTask.java
@@ -72,5 +72,9 @@ public class PlatformServiceCallThinTask extends AbstractPlatformServiceCallTask
             }, ClientException.class, "Failed to invoke platform service");
         }
 
+        /** {@inheritDoc} */
+        @Override protected void checkContextAttribute(TestPlatformService srv) {
+            // TODO IGNITE-15829 Remove this method override.
+        }
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs
index 8bd55a1..78dc48e 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs
@@ -163,6 +163,9 @@ namespace Apache.Ignite.Core.Tests.Services
 
             /** */
             BinarizableTestValue AddOne(BinarizableTestValue val);
+
+            /** */
+            string contextAttribute(string name);
         }
 
         #pragma warning disable 649
@@ -174,6 +177,9 @@ namespace Apache.Ignite.Core.Tests.Services
             [InstanceResource]
             private IIgnite _grid;
 
+            /** */
+            private IServiceContext _ctx;
+
             /** <inheritdoc /> */
             public Guid NodeId
             {
@@ -247,10 +253,15 @@ namespace Apache.Ignite.Core.Tests.Services
                 };
             }
 
+            public string contextAttribute(string name)
+            {
+                return _ctx.CurrentCallContext.GetAttribute(name);
+            }
+
             /** <inheritdoc /> */
             public void Init(IServiceContext context)
             {
-                // No-op.
+                _ctx = context;
             }
 
             /** <inheritdoc /> */
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs
index fe714c1..890b68d 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs
@@ -209,5 +209,8 @@ namespace Apache.Ignite.Core.Tests.Services
 
         /** */
         void sleep(long delayMs);
+
+        /** */
+        object contextAttribute(string name);
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs
index e5840e0..37dec5c 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs
@@ -394,5 +394,11 @@ namespace Apache.Ignite.Core.Tests.Services
         {
             _svc.sleep(delayMs);
         }
+
+        /** <inheritDoc /> */
+        public object contextAttribute(string name)
+        {
+            return _svc.contextAttribute(name);
+        }
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/PlatformTestService.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/PlatformTestService.cs
index 1ed48fe..733cb28 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/PlatformTestService.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/PlatformTestService.cs
@@ -45,6 +45,9 @@ namespace Apache.Ignite.Core.Tests.Services
         private bool _cancelled;
 
         /** */
+        private IServiceContext _context;
+
+        /** */
         public PlatformTestService()
         {
             // No-Op.
@@ -60,6 +63,7 @@ namespace Apache.Ignite.Core.Tests.Services
         /** <inheritDoc /> */
         public void Init(IServiceContext context)
         {
+            _context = context;
             _initialized = true;
         }
 
@@ -617,5 +621,11 @@ namespace Apache.Ignite.Core.Tests.Services
         {
             throw new NotImplementedException();
         }
+
+        /** <inheritDoc /> */
+        public object contextAttribute(string name)
+        {
+            return _context.CurrentCallContext.GetAttribute(name);
+        }
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs
index 51a79fc..37ca0f7 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs
@@ -284,7 +284,7 @@ namespace Apache.Ignite.Core.Tests.Services
                 inStream.WriteBool(SrvKeepBinary);  // WriteProxyMethod does not do this, but Java does
 
                 ServiceProxySerializer.WriteProxyMethod(_marsh.StartMarshal(inStream), method.Name,
-                    method, args, PlatformType.DotNet);
+                    method, args, PlatformType.DotNet, null);
 
                 inStream.SynchronizeOutput();
 
@@ -293,8 +293,9 @@ namespace Apache.Ignite.Core.Tests.Services
                 // 2) call InvokeServiceMethod
                 string mthdName;
                 object[] mthdArgs;
+                IServiceCallContext callCtx;
 
-                ServiceProxySerializer.ReadProxyMethod(inStream, _marsh, out mthdName, out mthdArgs);
+                ServiceProxySerializer.ReadProxyMethod(inStream, _marsh, out mthdName, out mthdArgs, out callCtx);
 
                 var result = ServiceProxyInvoker.InvokeServiceMethod(_svc, mthdName, mthdArgs);
 
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs
index cf058a9..a99ca37 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs
@@ -18,6 +18,7 @@
 namespace Apache.Ignite.Core.Tests.Services
 {
     using System;
+    using System.Collections;
     using System.Collections.Generic;
     using System.Threading.Tasks;
     using Apache.Ignite.Core.Cluster;
@@ -192,6 +193,12 @@ namespace Apache.Ignite.Core.Tests.Services
         {
             return _services.GetServiceProxy<T>(name, sticky);
         }
+        
+        /** <inheritDoc /> */
+        public T GetServiceProxy<T>(string name, bool sticky, IServiceCallContext callCtx) where T : class
+        {
+            return _services.GetServiceProxy<T>(name, sticky, callCtx);
+        }
 
         /** <inheritDoc /> */
         public dynamic GetDynamicServiceProxy(string name)
@@ -204,6 +211,12 @@ namespace Apache.Ignite.Core.Tests.Services
         {
             return _services.GetDynamicServiceProxy(name, sticky);
         }
+        
+        /** <inheritDoc /> */
+        public dynamic GetDynamicServiceProxy(string name, bool sticky, IServiceCallContext callCtx)
+        {
+            return _services.GetDynamicServiceProxy(name, sticky, callCtx);
+        }
 
         /** <inheritDoc /> */
         public IServices WithKeepBinary()
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
index af0f4bd..d393259 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
@@ -27,10 +27,12 @@ namespace Apache.Ignite.Core.Tests.Services
     using Apache.Ignite.Core.Cluster;
     using Apache.Ignite.Core.Common;
     using Apache.Ignite.Core.Impl;
+    using Apache.Ignite.Core.Impl.Binary;
     using Apache.Ignite.Core.Resource;
     using Apache.Ignite.Core.Services;
-    using NUnit.Framework;
+    using Apache.Ignite.Core.Tests.Compute;
     using Apache.Ignite.Platform.Model;
+    using NUnit.Framework;
 
     /// <summary>
     /// Services tests.
@@ -266,6 +268,58 @@ namespace Apache.Ignite.Core.Tests.Services
         }
 
         /// <summary>
+        /// Tests service call context.
+        /// </summary>
+        [Test]
+        public void TestServiceCallContext([Values(true, false)] bool binarizable)
+        {
+            var svc = binarizable
+                ? new TestIgniteServiceBinarizable {TestProperty = 17}
+                : new TestIgniteServiceSerializable {TestProperty = 17};
+
+            Services.DeployClusterSingleton(SvcName, svc);
+
+            foreach (var grid in Grids)
+            {
+                var nodeId = grid.GetCluster().ForLocal().GetNode().Id;
+
+                var attrName = grid.Name;
+                var attrBinName = "bin-" + grid.Name;
+                var attrValue = nodeId.ToString();
+                var attrBinValue = nodeId.ToByteArray();
+
+                var svc0 = grid.GetServices().GetService<ITestIgniteService>(SvcName);
+
+                if (svc0 != null)
+                    Assert.IsNull(svc0.ContextAttribute(attrName));
+
+                var ctx = new ServiceCallContextBuilder()
+                    .Set(attrName, attrValue)
+                    .Set(attrBinName, attrBinValue)
+                    .Build();
+
+                var proxy = grid.GetServices().GetServiceProxy<ITestIgniteService>(SvcName, false, ctx);
+                
+                Assert.IsNull(proxy.ContextAttribute("not-exist-attribute"));
+                Assert.IsNull(proxy.ContextBinaryAttribute("not-exist-attribute"));
+
+                var stickyProxy = grid.GetServices().GetServiceProxy<ITestIgniteService>(SvcName, true, ctx);
+                var dynamicProxy = grid.GetServices().GetDynamicServiceProxy(SvcName, false, ctx);
+                var dynamicStickyProxy = grid.GetServices().GetDynamicServiceProxy(SvcName, true, ctx);
+
+                Assert.AreEqual(attrValue, proxy.ContextAttribute(attrName));
+                Assert.AreEqual(attrValue, stickyProxy.ContextAttribute(attrName));
+                Assert.AreEqual(attrValue, dynamicProxy.ContextAttribute(attrName));
+                Assert.AreEqual(attrValue, dynamicStickyProxy.ContextAttribute(attrName));
+                
+                Assert.AreEqual(attrBinValue, proxy.ContextBinaryAttribute(attrBinName));
+                Assert.AreEqual(attrBinValue, stickyProxy.ContextBinaryAttribute(attrBinName));
+                Assert.AreEqual(attrBinValue, dynamicProxy.ContextBinaryAttribute(attrBinName));
+                Assert.AreEqual(attrBinValue, dynamicStickyProxy.ContextBinaryAttribute(attrBinName));
+            }
+        }
+
+        /// <summary>
         /// Tests service proxy.
         /// </summary>
         [Test]
@@ -872,7 +926,7 @@ namespace Apache.Ignite.Core.Tests.Services
             var descriptor = _client.GetServices().GetServiceDescriptors().Single(x => x.Name == javaSvcName);
             Assert.AreEqual(javaSvcName, descriptor.Name);
 
-            var svc = _client.GetServices().GetServiceProxy<IJavaService>(javaSvcName, false);
+            var svc = _client.GetServices().GetServiceProxy<IJavaService>(javaSvcName, false, callContext());
             var binSvc = _client.GetServices().WithKeepBinary().WithServerKeepBinary()
                 .GetServiceProxy<IJavaService>(javaSvcName, false);
 
@@ -898,7 +952,7 @@ namespace Apache.Ignite.Core.Tests.Services
             var descriptor = Services.GetServiceDescriptors().Single(x => x.Name == javaSvcName);
             Assert.AreEqual(javaSvcName, descriptor.Name);
 
-            var svc = Services.GetServiceProxy<IJavaService>(javaSvcName, false);
+            var svc = Services.GetServiceProxy<IJavaService>(javaSvcName, false, callContext());
             var binSvc = Services.WithKeepBinary().WithServerKeepBinary()
                 .GetServiceProxy<IJavaService>(javaSvcName, false);
 
@@ -920,7 +974,7 @@ namespace Apache.Ignite.Core.Tests.Services
             // Deploy Java service
             var javaSvcName = TestUtils.DeployJavaService(Grid1);
 
-            var svc = new JavaServiceDynamicProxy(Grid1.GetServices().GetDynamicServiceProxy(javaSvcName, true));
+            var svc = new JavaServiceDynamicProxy(Grid1.GetServices().GetDynamicServiceProxy(javaSvcName, true, callContext()));
 
             DoTestService(svc);
 
@@ -954,7 +1008,7 @@ namespace Apache.Ignite.Core.Tests.Services
 
             Services.DeployClusterSingleton(platformSvcName, new PlatformTestService());
 
-            var svc = svcsForProxy.GetServiceProxy<IJavaService>(platformSvcName);
+            var svc = svcsForProxy.GetServiceProxy<IJavaService>(platformSvcName, false, callContext());
 
             DoTestService(svc);
 
@@ -1092,6 +1146,7 @@ namespace Apache.Ignite.Core.Tests.Services
             Assert.AreEqual(dt1, cache.Get(3));
             Assert.AreEqual(dt2, cache.Get(4));
 
+            Assert.AreEqual("value", svc.contextAttribute("attr"));
 
 #if NETCOREAPP
             //This Date in Europe/Moscow have offset +4.
@@ -1117,6 +1172,15 @@ namespace Apache.Ignite.Core.Tests.Services
         }
 
         /// <summary>
+        /// Creates a test caller context.
+        /// </summary>
+        /// <returns>Caller context.</returns>
+        private IServiceCallContext callContext()
+        {
+            return new ServiceCallContextBuilder().Set("attr", "value").Build();
+        }
+
+        /// <summary>
         /// Tests handling of the Java exception in the .Net.
         /// </summary>
         public void DoTestJavaExceptions(IJavaService svc, bool isClient = false)
@@ -1290,7 +1354,7 @@ namespace Apache.Ignite.Core.Tests.Services
         {
             if (!CompactFooter)
             {
-                springConfigUrl = Compute.ComputeApiTestFullFooter.ReplaceFooterSetting(springConfigUrl);
+                springConfigUrl = ComputeApiTestFullFooter.ReplaceFooterSetting(springConfigUrl);
             }
 
             return new IgniteConfiguration(TestUtils.GetTestConfiguration())
@@ -1461,6 +1525,12 @@ namespace Apache.Ignite.Core.Tests.Services
 
             /** */
             int TestOverload(int count, Parameter[] param);
+
+            /** */
+            object ContextAttribute(string name);
+            
+            /** */
+            object ContextBinaryAttribute(string name);
         }
 
         /// <summary>
@@ -1492,6 +1562,9 @@ namespace Apache.Ignite.Core.Tests.Services
             // ReSharper disable once UnassignedField.Local
             private IIgnite _grid;
 
+            // Service context.
+            private IServiceContext _context;
+
             /** <inheritdoc /> */
             public int TestProperty { get; set; }
 
@@ -1584,6 +1657,22 @@ namespace Apache.Ignite.Core.Tests.Services
             }
 
             /** <inheritdoc /> */
+            public object ContextAttribute(string name)
+            {
+                IServiceCallContext ctx = _context.CurrentCallContext;
+                
+                return ctx == null ? null : ctx.GetAttribute(name);
+            }
+            
+            /** <inheritdoc /> */
+            public object ContextBinaryAttribute(string name)
+            {
+                IServiceCallContext ctx = _context.CurrentCallContext;
+                
+                return ctx == null ? null : ctx.GetBinaryAttribute(name);
+            }
+
+            /** <inheritdoc /> */
             public void Init(IServiceContext context)
             {
                 lock (this)
@@ -1596,6 +1685,8 @@ namespace Apache.Ignite.Core.Tests.Services
                     Assert.IsFalse(context.IsCancelled);
                     Initialized = true;
                 }
+
+                _context = context;
             }
 
             /** <inheritdoc /> */
@@ -1773,14 +1864,14 @@ namespace Apache.Ignite.Core.Tests.Services
                 if (date.Kind == DateTimeKind.Local)
                     date = date.ToUniversalTime();
 
-                Impl.Binary.BinaryUtils.ToJavaDate(date, out high, out low);
+                BinaryUtils.ToJavaDate(date, out high, out low);
             }
 
             /** <inheritdoc /> */
             public DateTime FromJavaTicks(long high, int low)
             {
                 return new DateTime(
-                    Impl.Binary.BinaryUtils.JavaDateTicks + high * TimeSpan.TicksPerMillisecond + low / 100,
+                    BinaryUtils.JavaDateTicks + high * TimeSpan.TicksPerMillisecond + low / 100,
                     DateTimeKind.Utc);
             }
         }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Common/IgniteExperimentalAttribute.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Common/IgniteExperimentalAttribute.cs
index 81b0e51..e5fd46f 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Common/IgniteExperimentalAttribute.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Common/IgniteExperimentalAttribute.cs
@@ -28,8 +28,8 @@ namespace Apache.Ignite.Core.Common
     /// allowed for such APIs: API may be removed, changed or stabilized in future Ignite releases
     /// (both minor and maintenance).
     /// </summary>
-    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Enum | 
-                    AttributeTargets.Property | AttributeTargets.Field)]
+    [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Method |  
+                    AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field)]
     public sealed class IgniteExperimentalAttribute : Attribute
     {
         // No-op.
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceCallContext.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceCallContext.cs
new file mode 100644
index 0000000..8f5d61b
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceCallContext.cs
@@ -0,0 +1,64 @@
+/*
+ * 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.Core.Impl.Services
+{
+    using System.Collections;
+    using Apache.Ignite.Core.Impl.Common;
+    using Apache.Ignite.Core.Services;
+
+    /// <summary>
+    /// Service call context implementation.
+    /// </summary>
+    internal class ServiceCallContext : IServiceCallContext
+    {
+        /** Context attributes. */
+        private IDictionary _attrs;
+
+        /// <summary>
+        /// Constructs context from dictionary.
+        /// </summary>
+        /// <param name="attrs">Context attributes.</param>
+        internal ServiceCallContext(IDictionary attrs)
+        {
+            IgniteArgumentCheck.NotNull(attrs, "attrs");
+
+            _attrs = attrs;
+        }
+
+        /** <inheritDoc /> */
+        public string GetAttribute(string name)
+        {
+            return (string) _attrs[name];
+        }
+
+        /** <inheritDoc /> */
+        public byte[] GetBinaryAttribute(string name)
+        {
+            return (byte[]) _attrs[name];
+        }
+        
+        /// <summary>
+        /// Gets call context attributes.
+        /// </summary>
+        /// <returns>Service call context attributes.</returns>
+        internal IDictionary Values()
+        {
+            return _attrs;
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceContext.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceContext.cs
index 2532b70..94a6ac8 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceContext.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceContext.cs
@@ -19,6 +19,7 @@ namespace Apache.Ignite.Core.Impl.Services
 {
     using System;
     using System.Diagnostics;
+    using System.Threading;
     using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Services;
 
@@ -27,6 +28,9 @@ namespace Apache.Ignite.Core.Impl.Services
     /// </summary>
     internal class ServiceContext : IServiceContext
     {
+        /** Service call context of the current thread. */
+        private static readonly ThreadLocal<IServiceCallContext> locCallCtx = new ThreadLocal<IServiceCallContext>();
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ServiceContext"/> class.
         /// </summary>
@@ -56,5 +60,19 @@ namespace Apache.Ignite.Core.Impl.Services
 
         /** <inheritdoc /> */
         public object AffinityKey { get; private set; }
+
+        /** <inheritdoc /> */
+        public IServiceCallContext CurrentCallContext
+        {
+            get { return locCallCtx.Value; }
+        }
+
+        /// <summary>
+        /// Sets service call context for the current thread.
+        /// </summary>
+        /// <param name="callCtx">Service call context for the current thread.</param>
+        internal static void SetCurrentCallContext(IServiceCallContext callCtx) {
+            locCallCtx.Value = callCtx;
+        }
     }
 }
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs
index e28f4d7..5614fd5 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs
@@ -41,8 +41,9 @@ namespace Apache.Ignite.Core.Impl.Services
         /// <param name="method">Method (optional, can be null).</param>
         /// <param name="arguments">Arguments.</param>
         /// <param name="platformType">The platform.</param>
+        /// <param name="callCtx">Service call context.</param>
         public static void WriteProxyMethod(BinaryWriter writer, string methodName, MethodBase method,
-            object[] arguments, PlatformType platformType)
+            object[] arguments, PlatformType platformType, IServiceCallContext callCtx)
         {
             Debug.Assert(writer != null);
 
@@ -76,6 +77,8 @@ namespace Apache.Ignite.Core.Impl.Services
             }
             else
                 writer.WriteBoolean(false);
+
+            writer.WriteDictionary(callCtx == null ? null : ((ServiceCallContext) callCtx).Values());
         }
 
         /// <summary>
@@ -85,8 +88,9 @@ namespace Apache.Ignite.Core.Impl.Services
         /// <param name="marsh">Marshaller.</param>
         /// <param name="mthdName">Method name.</param>
         /// <param name="mthdArgs">Method arguments.</param>
+        /// <param name="callCtx">Service call context.</param>
         public static void ReadProxyMethod(IBinaryStream stream, Marshaller marsh, 
-            out string mthdName, out object[] mthdArgs)
+            out string mthdName, out object[] mthdArgs, out IServiceCallContext callCtx)
         {
             var reader = marsh.StartUnmarshal(stream);
 
@@ -106,6 +110,10 @@ namespace Apache.Ignite.Core.Impl.Services
             }
             else
                 mthdArgs = null;
+
+            var attrs = reader.ReadDictionary();
+
+            callCtx = attrs == null ? null : new ServiceCallContext(attrs);
         }
 
         /// <summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs
index 3d5f4eb..7ec4b3a 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs
@@ -366,15 +366,21 @@ namespace Apache.Ignite.Core.Impl.Services
         /** <inheritDoc /> */
         public T GetServiceProxy<T>(string name, bool sticky) where T : class
         {
+            return GetServiceProxy<T>(name, sticky, null);
+        }
+
+        /** <inheritDoc /> */
+        public T GetServiceProxy<T>(string name, bool sticky, IServiceCallContext callCtx) where T : class
+        {
             IgniteArgumentCheck.NotNullOrEmpty(name, "name");
             IgniteArgumentCheck.Ensure(typeof(T).IsInterface, "T", 
                 "Service proxy type should be an interface: " + typeof(T));
 
+            T locInst;
+
             // In local scenario try to return service instance itself instead of a proxy
             // Get as object because proxy interface may be different from real interface
-            var locInst = GetService<object>(name) as T;
-
-            if (locInst != null)
+            if (callCtx == null && (locInst = GetService<object>(name) as T) != null)
                 return locInst;
 
             var javaProxy = DoOutOpObject(OpServiceProxy, w =>
@@ -386,7 +392,7 @@ namespace Apache.Ignite.Core.Impl.Services
             var platform = GetServiceDescriptors().Cast<ServiceDescriptor>().Single(x => x.Name == name).PlatformType;
 
             return ServiceProxyFactory<T>.CreateProxy((method, args) =>
-                InvokeProxyMethod(javaProxy, method.Name, method, args, platform));
+                InvokeProxyMethod(javaProxy, method.Name, method, args, platform, callCtx));
         }
 
         /** <inheritDoc /> */
@@ -398,14 +404,21 @@ namespace Apache.Ignite.Core.Impl.Services
         /** <inheritDoc /> */
         public dynamic GetDynamicServiceProxy(string name, bool sticky)
         {
+            return GetDynamicServiceProxy(name, sticky, null);
+        }
+
+        /** <inheritDoc /> */
+        public dynamic GetDynamicServiceProxy(string name, bool sticky, IServiceCallContext callCtx)
+        {
             IgniteArgumentCheck.NotNullOrEmpty(name, "name");
 
             // In local scenario try to return service instance itself instead of a proxy
-            var locInst = GetService<object>(name);
-
-            if (locInst != null)
+            if (callCtx == null)
             {
-                return locInst;
+                var locInst = GetService<object>(name);
+
+                if (locInst != null)
+                    return locInst;
             }
 
             var javaProxy = DoOutOpObject(OpServiceProxy, w =>
@@ -417,7 +430,7 @@ namespace Apache.Ignite.Core.Impl.Services
             var platform = GetServiceDescriptors().Cast<ServiceDescriptor>().Single(x => x.Name == name).PlatformType;
 
             return new DynamicServiceProxy((methodName, args) =>
-                InvokeProxyMethod(javaProxy, methodName, null, args, platform));
+                InvokeProxyMethod(javaProxy, methodName, null, args, platform, callCtx));
         }
 
         /// <summary>
@@ -428,11 +441,12 @@ namespace Apache.Ignite.Core.Impl.Services
         /// <param name="method">Method to invoke.</param>
         /// <param name="args">Arguments.</param>
         /// <param name="platformType">The platform.</param>
+        /// <param name="callCtx">Service call context.</param>
         /// <returns>
         /// Invocation result.
         /// </returns>
         private object InvokeProxyMethod(IPlatformTargetInternal proxy, string methodName,
-            MethodBase method, object[] args, PlatformType platformType)
+            MethodBase method, object[] args, PlatformType platformType, IServiceCallContext callCtx)
         {
             bool locRegisterSameJavaType = Marshaller.RegisterSameJavaTypeTl.Value;
 
@@ -444,7 +458,7 @@ namespace Apache.Ignite.Core.Impl.Services
             try
             {
                 return DoOutInOp(OpInvokeMethod,
-                    writer => ServiceProxySerializer.WriteProxyMethod(writer, methodName, method, args, platformType),
+                    writer => ServiceProxySerializer.WriteProxyMethod(writer, methodName, method, args, platformType, callCtx),
                     (stream, res) => ServiceProxySerializer.ReadInvocationResult(stream, Marshaller, _keepBinary),
                     proxy);
             }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbacks.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbacks.cs
index f6d90b3..c26a441 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbacks.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Unmanaged/UnmanagedCallbacks.cs
@@ -18,6 +18,7 @@
 namespace Apache.Ignite.Core.Impl.Unmanaged
 {
     using System;
+    using System.Collections;
     using System.Collections.Generic;
     using System.Diagnostics;
     using System.Diagnostics.CodeAnalysis;
@@ -1105,16 +1106,28 @@ namespace Apache.Ignite.Core.Impl.Unmanaged
 
                 string mthdName;
                 object[] mthdArgs;
+                IServiceCallContext callCtx;
 
-                ServiceProxySerializer.ReadProxyMethod(stream, _ignite.Marshaller, out mthdName, out mthdArgs);
+                ServiceProxySerializer.ReadProxyMethod(stream, _ignite.Marshaller, out mthdName, out mthdArgs, out callCtx);
+                
+                if (callCtx != null)
+                    ServiceContext.SetCurrentCallContext(callCtx);
 
-                var result = ServiceProxyInvoker.InvokeServiceMethod(svc, mthdName, mthdArgs);
+                try
+                {
+                    var result = ServiceProxyInvoker.InvokeServiceMethod(svc, mthdName, mthdArgs);
 
-                stream.Reset();
+                    stream.Reset();
 
-                ServiceProxySerializer.WriteInvocationResult(stream, _ignite.Marshaller, result.Key, result.Value);
+                    ServiceProxySerializer.WriteInvocationResult(stream, _ignite.Marshaller, result.Key, result.Value);
 
-                stream.SynchronizeOutput();
+                    stream.SynchronizeOutput();
+                }
+                finally
+                {
+                    if (callCtx != null)
+                        ServiceContext.SetCurrentCallContext(null);
+                }
 
                 return 0;
             }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServiceCallContext.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServiceCallContext.cs
new file mode 100644
index 0000000..d5d7c6d
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServiceCallContext.cs
@@ -0,0 +1,78 @@
+/*
+ * 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.Core.Services
+{
+    using Apache.Ignite.Core.Common;
+
+    /// <summary>
+    /// Represents service call context.
+    /// <para />
+    /// This context is implicitly passed to the service and can be retrieved inside the service
+    /// using <see cref="IServiceContext.CurrentCallContext()"/>. It is accessible only
+    /// from the local thread during the execution of a service method.
+    /// <para />
+    /// Use <see cref="ServiceCallContextBuilder"/> to instantiate the context.
+    /// <para />
+    /// <b>Note</b>: passing the context to the service may lead to performance overhead,
+    /// so it should only be used for "middleware" tasks.
+    /// <para />
+    /// Usage example:
+    /// <code>
+    /// // Service implementation.
+    /// public class HelloServiceImpl : HelloService
+    /// {
+    ///     private IServiceContext ctx;
+    ///
+    ///     public void Init(IServiceContext ctx)
+    ///     {
+    ///         this.ctx = ctx;
+    ///     }
+    /// 
+    ///     public string Call(string msg)
+    ///     {
+    ///         return msg + ctx.CurrentCallContext.Attribute("user");
+    ///     }
+    ///     ...
+    /// }
+    /// ...
+    ///
+    /// // Call this service with context.
+    /// IServiceCallContext callCtx = new ServiceCallContextBuilder().Set("user", "John").build();
+    /// HelloService helloSvc = ignite.GetServices().GetServiceProxy&lt;HelloService&gt;("hello-service", false, callCtx);
+    /// // Print "Hello John".
+    /// Console.WriteLine( helloSvc.call("Hello ") );
+    /// </code>
+    /// </summary>
+    [IgniteExperimental]
+    public interface IServiceCallContext
+    {
+        /// <summary>
+        /// Gets the string attribute.
+        /// </summary>
+        /// <param name="name">Attribute name.</param>
+        /// <returns>String attribute value.</returns>
+        string GetAttribute(string name);
+
+        /// <summary>
+        /// Gets the binary attribute.
+        /// </summary>
+        /// <param name="name">Attribute name.</param>
+        /// <returns>Binary attribute value.</returns>
+        byte[] GetBinaryAttribute(string name);
+    }
+}
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServiceContext.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServiceContext.cs
index 50c3f14..927ba70 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServiceContext.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServiceContext.cs
@@ -18,6 +18,7 @@
 namespace Apache.Ignite.Core.Services
 {
     using System;
+    using Apache.Ignite.Core.Common;
 
     /// <summary>
     /// Represents service execution context.
@@ -65,5 +66,13 @@ namespace Apache.Ignite.Core.Services
         /// Affinity key, possibly null.
         /// </value>
         object AffinityKey { get; }
+
+        /// <summary>
+        /// Gets context of the current service call. 
+        /// </summary>
+        /// <returns>Context of the current service call.</returns>
+        /// <seealso cref="IServiceCallContext"/>
+        [IgniteExperimental]
+        IServiceCallContext CurrentCallContext { get; }
     }
 }
\ No newline at end of file
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs
index 84c23fa..569712e 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs
@@ -21,6 +21,7 @@ namespace Apache.Ignite.Core.Services
     using System.Diagnostics.CodeAnalysis;
     using System.Threading.Tasks;
     using Apache.Ignite.Core.Cluster;
+    using Apache.Ignite.Core.Common;
 
     /// <summary>
     /// Defines functionality to deploy distributed services in the Ignite.
@@ -272,6 +273,20 @@ namespace Apache.Ignite.Core.Services
         T GetServiceProxy<T>(string name, bool sticky) where T : class;
 
         /// <summary>
+        /// Gets a remote handle on the service with the specified caller context.
+        /// The proxy is dynamically created and provided for the specified service.
+        /// </summary>
+        /// <typeparam name="T">Service type.</typeparam>
+        /// <param name="name">Service name.</param>
+        /// <param name="sticky">Whether or not Ignite should always contact the same remote
+        /// service or try to load-balance between services.</param>
+        /// <param name="callCtx">Service call context.</param>
+        /// <returns>Proxy over service.</returns>
+        /// <seealso cref="IServiceCallContext"/>
+        [IgniteExperimental]
+        T GetServiceProxy<T>(string name, bool sticky, IServiceCallContext callCtx) where T : class;
+
+        /// <summary>
         /// Gets a remote handle on the service as a dynamic object. If service is available locally,
         /// then local instance is returned, otherwise, a remote proxy is dynamically
         /// created and provided for the specified service.
@@ -296,6 +311,22 @@ namespace Apache.Ignite.Core.Services
         /// service or try to load-balance between services.</param>
         /// <returns>Either proxy over remote service or local service if it is deployed locally.</returns>
         dynamic GetDynamicServiceProxy(string name, bool sticky);
+        
+        /// <summary>
+        /// Gets a remote handle on the service with the specified caller context.
+        /// The proxy is dynamically created and provided for the specified service.
+        /// <para />
+        /// This method utilizes <c>dynamic</c> feature of the language and does not require any
+        /// service interfaces or classes. Java services can be accessed as well as .NET services.
+        /// </summary>
+        /// <param name="name">Service name.</param>
+        /// <param name="sticky">Whether or not Ignite should always contact the same remote
+        /// service or try to load-balance between services.</param>
+        /// <param name="callCtx">Service call context.</param>
+        /// <returns>Proxy over service.</returns>
+        /// <seealso cref="IServiceCallContext"/>
+        [IgniteExperimental]
+        dynamic GetDynamicServiceProxy(string name, bool sticky, IServiceCallContext callCtx);
 
         /// <summary>
         /// Returns an instance with binary mode enabled.
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Services/ServiceCallContextBuilder.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Services/ServiceCallContextBuilder.cs
new file mode 100644
index 0000000..2f0604c
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Services/ServiceCallContextBuilder.cs
@@ -0,0 +1,82 @@
+/*
+ * 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.Core.Services
+{
+    using System;
+    using System.Collections;
+    using System.Linq;
+    using Apache.Ignite.Core.Common;
+    using Apache.Ignite.Core.Impl.Common;
+    using Apache.Ignite.Core.Impl.Services;
+
+    /// <summary>
+    /// Service call context builder.
+    /// </summary>
+    [IgniteExperimental]
+    public class ServiceCallContextBuilder
+    {
+        /** Context attributes. */
+        private readonly Hashtable _attrs = new Hashtable();
+        
+        /// <summary>
+        /// Set string attribute.
+        /// </summary>
+        /// <param name="name">Attribute name.</param>
+        /// <param name="value">Attribute value.</param>
+        /// <returns>This for chaining.</returns>
+        public ServiceCallContextBuilder Set(string name, string value)
+        {
+            IgniteArgumentCheck.NotNullOrEmpty(name, "name");
+            IgniteArgumentCheck.NotNull(value, "value");
+
+            _attrs[name] = value;
+
+            return this;
+        }
+
+        /// <summary>
+        /// Set binary attribute.
+        /// <p/>
+        /// <b>Note:</b> it is recommended to pass a copy of the array if the original can be changed later.
+        /// </summary>
+        /// <param name="name">Attribute name.</param>
+        /// <param name="value">Attribute value.</param>
+        /// <returns>This for chaining.</returns>
+        public ServiceCallContextBuilder Set(string name, byte[] value)
+        {
+            IgniteArgumentCheck.NotNullOrEmpty(name, "name");
+            IgniteArgumentCheck.NotNull(value, "value");
+
+            _attrs[name] = value;
+            
+            return this;
+        }
+
+        /// <summary>
+        /// Create context.
+        /// </summary>
+        /// <returns>Service call context.</returns>
+        public IServiceCallContext Build()
+        {
+            if (_attrs.Count == 0)
+                throw new InvalidOperationException("Cannot create an empty context.");
+
+            return new ServiceCallContext((Hashtable)_attrs.Clone());
+        }
+    }
+}
\ No newline at end of file