IGNITE-7561 .NET: Add IServices.GetDynamicServiceProxy
authorPavel Tupitsyn <ptupitsyn@apache.org>
Thu, 1 Feb 2018 11:53:16 +0000 (14:53 +0300)
committerPavel Tupitsyn <ptupitsyn@apache.org>
Thu, 1 Feb 2018 11:53:16 +0000 (14:53 +0300)
This closes #3457

modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs
modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs
modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/DynamicServiceProxy.cs [new file with mode: 0644]
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs
modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs
modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs

index 6146146..cb36d1d 100644 (file)
@@ -283,7 +283,8 @@ namespace Apache.Ignite.Core.Tests.Services
                 // 1) Write to a stream
                 inStream.WriteBool(SrvKeepBinary);  // WriteProxyMethod does not do this, but Java does
 
-                ServiceProxySerializer.WriteProxyMethod(_marsh.StartMarshal(inStream), method, args, Platform.DotNet);
+                ServiceProxySerializer.WriteProxyMethod(_marsh.StartMarshal(inStream), method.Name, 
+                    method, args, Platform.DotNet);
 
                 inStream.SynchronizeOutput();
 
index 18db5d5..cf058a9 100644 (file)
@@ -91,7 +91,7 @@ namespace Apache.Ignite.Core.Tests.Services
             }
             catch (AggregateException ex)
             {
-                throw ex.InnerException;
+                throw ex.InnerException ?? ex;
             }
         }
 
@@ -110,7 +110,7 @@ namespace Apache.Ignite.Core.Tests.Services
             }
             catch (AggregateException ex)
             {
-                throw ex.InnerException;
+                throw ex.InnerException ?? ex;
             }
         }
 
@@ -129,7 +129,7 @@ namespace Apache.Ignite.Core.Tests.Services
             }
             catch (AggregateException ex)
             {
-                throw ex.InnerException;
+                throw ex.InnerException ?? ex;
             }
         }
 
@@ -194,6 +194,18 @@ namespace Apache.Ignite.Core.Tests.Services
         }
 
         /** <inheritDoc /> */
+        public dynamic GetDynamicServiceProxy(string name)
+        {
+            return _services.GetDynamicServiceProxy(name);
+        }
+
+        /** <inheritDoc /> */
+        public dynamic GetDynamicServiceProxy(string name, bool sticky)
+        {
+            return _services.GetDynamicServiceProxy(name, sticky);
+        }
+
+        /** <inheritDoc /> */
         public IServices WithKeepBinary()
         {
             return new ServicesAsyncWrapper(_services.WithKeepBinary());
index b8f4cdf..c6a8e0b 100644 (file)
@@ -318,6 +318,67 @@ namespace Apache.Ignite.Core.Tests.Services
         }
 
         /// <summary>
+        /// Tests dynamic service proxies.
+        /// </summary>
+        [Test]
+        public void TestGetDynamicServiceProxy()
+        {
+            // Deploy to remotes.
+            var svc = new TestIgniteServiceSerializable { TestProperty = 37 };
+            Grid1.GetCluster().ForRemotes().GetServices().DeployNodeSingleton(SvcName, svc);
+
+            // Make sure there is no local instance on grid3
+            Assert.IsNull(Grid3.GetServices().GetService<ITestIgniteService>(SvcName));
+
+            // Get proxy.
+            dynamic prx = Grid3.GetServices().GetDynamicServiceProxy(SvcName, false);
+
+            // Property getter.
+            Assert.AreEqual(37, prx.TestProperty);
+            Assert.IsTrue(prx.Initialized);
+            Assert.IsTrue(prx.Executed);
+            Assert.IsFalse(prx.Cancelled);
+            Assert.AreEqual(SvcName, prx.LastCallContextName);
+
+            // Property setter.
+            prx.TestProperty = 42;
+            Assert.AreEqual(42, prx.TestProperty);
+
+            // Method invoke.
+            Assert.AreEqual(prx.ToString(), svc.ToString());
+            Assert.AreEqual("baz", prx.Method("baz"));
+
+            // Non-existent member.
+            var ex = Assert.Throws<ServiceInvocationException>(() => prx.FooBar(1));
+            Assert.AreEqual(
+                string.Format("Failed to invoke proxy: there is no method 'FooBar' in type '{0}' with 1 arguments",
+                    typeof(TestIgniteServiceSerializable)), (ex.InnerException ?? ex).Message);
+
+            // Exception in service.
+            ex = Assert.Throws<ServiceInvocationException>(() => prx.ErrMethod(123));
+            Assert.AreEqual("ExpectedException", (ex.InnerException ?? ex).Message.Substring(0, 17));
+        }
+
+        /// <summary>
+        /// Tests dynamic service proxies with local service instance.
+        /// </summary>
+        [Test]
+        public void TestGetDynamicServiceProxyLocal()
+        {
+            // Deploy to all nodes.
+            var svc = new TestIgniteServiceSerializable { TestProperty = 37 };
+            Grid1.GetServices().DeployNodeSingleton(SvcName, svc);
+
+            // Make sure there is an instance on grid1.
+            var svcInst = Grid1.GetServices().GetService<ITestIgniteService>(SvcName);
+            Assert.IsNotNull(svcInst);
+
+            // Get dynamic proxy that simply wraps the service instance.
+            var prx = Grid1.GetServices().GetDynamicServiceProxy(SvcName);
+            Assert.AreSame(prx, svcInst);
+        }
+
+        /// <summary>
         /// Tests the duck typing: proxy interface can be different from actual service interface, 
         /// only called method signature should be compatible.
         /// </summary>
@@ -729,13 +790,9 @@ namespace Apache.Ignite.Core.Tests.Services
         [Test]
         public void TestCallJavaService()
         {
-            const string javaSvcName = "javaService";
-
             // Deploy Java service
-            Grid1.GetCompute()
-                .ExecuteJavaTask<object>("org.apache.ignite.platform.PlatformDeployServiceTask", javaSvcName);
-
-            TestUtils.WaitForCondition(() => Services.GetServiceDescriptors().Any(x => x.Name == javaSvcName), 1000);
+            const string javaSvcName = "javaService";
+            DeployJavaService(javaSvcName);
 
             // Verify decriptor
             var descriptor = Services.GetServiceDescriptors().Single(x => x.Name == javaSvcName);
@@ -814,6 +871,85 @@ namespace Apache.Ignite.Core.Tests.Services
         }
 
         /// <summary>
+        /// Tests Java service invocation with dynamic proxy.
+        /// </summary>
+        [Test]
+        public void TestCallJavaServiceDynamicProxy()
+        {
+            const string javaSvcName = "javaService";
+            DeployJavaService(javaSvcName);
+
+            var svc = Grid1.GetServices().GetDynamicServiceProxy(javaSvcName, true);
+
+            // Basics
+            Assert.IsTrue(svc.isInitialized());
+            Assert.IsTrue(TestUtils.WaitForCondition(() => svc.isExecuted(), 500));
+            Assert.IsFalse(svc.isCancelled());
+
+            // Primitives
+            Assert.AreEqual(4, svc.test((byte)3));
+            Assert.AreEqual(5, svc.test((short)4));
+            Assert.AreEqual(6, svc.test(5));
+            Assert.AreEqual(6, svc.test((long)5));
+            Assert.AreEqual(3.8f, svc.test(2.3f));
+            Assert.AreEqual(5.8, svc.test(3.3));
+            Assert.IsFalse(svc.test(true));
+            Assert.AreEqual('b', svc.test('a'));
+            Assert.AreEqual("Foo!", svc.test("Foo"));
+
+            // Nullables (Java wrapper types)
+            Assert.AreEqual(4, svc.testWrapper(3));
+            Assert.AreEqual(5, svc.testWrapper((short?)4));
+            Assert.AreEqual(6, svc.testWrapper((int?)5));
+            Assert.AreEqual(6, svc.testWrapper((long?)5));
+            Assert.AreEqual(3.8f, svc.testWrapper(2.3f));
+            Assert.AreEqual(5.8, svc.testWrapper(3.3));
+            Assert.AreEqual(false, svc.testWrapper(true));
+            Assert.AreEqual('b', svc.testWrapper('a'));
+
+            // Arrays
+            Assert.AreEqual(new byte[] { 2, 3, 4 }, svc.testArray(new byte[] { 1, 2, 3 }));
+            Assert.AreEqual(new short[] { 2, 3, 4 }, svc.testArray(new short[] { 1, 2, 3 }));
+            Assert.AreEqual(new[] { 2, 3, 4 }, svc.testArray(new[] { 1, 2, 3 }));
+            Assert.AreEqual(new long[] { 2, 3, 4 }, svc.testArray(new long[] { 1, 2, 3 }));
+            Assert.AreEqual(new float[] { 2, 3, 4 }, svc.testArray(new float[] { 1, 2, 3 }));
+            Assert.AreEqual(new double[] { 2, 3, 4 }, svc.testArray(new double[] { 1, 2, 3 }));
+            Assert.AreEqual(new[] { "a1", "b1" }, svc.testArray(new[] { "a", "b" }));
+            Assert.AreEqual(new[] { 'c', 'd' }, svc.testArray(new[] { 'b', 'c' }));
+            Assert.AreEqual(new[] { false, true, false }, svc.testArray(new[] { true, false, true }));
+
+            // Nulls
+            Assert.AreEqual(9, svc.testNull(8));
+            Assert.IsNull(svc.testNull(null));
+
+            // Overloads
+            Assert.AreEqual(3, svc.test(2, "1"));
+            Assert.AreEqual(3, svc.test("1", 2));
+
+            // Binary
+            Assert.AreEqual(7, svc.testBinarizable(new PlatformComputeBinarizable { Field = 6 }).Field);
+
+            // Binary object
+            var binSvc = Services.WithKeepBinary().WithServerKeepBinary().GetDynamicServiceProxy(javaSvcName);
+
+            Assert.AreEqual(15,
+                binSvc.testBinaryObject(
+                    Grid1.GetBinary().ToBinary<IBinaryObject>(new PlatformComputeBinarizable { Field = 6 }))
+                    .GetField<int>("Field"));
+        }
+
+        /// <summary>
+        /// Deploys the java service.
+        /// </summary>
+        private void DeployJavaService(string javaSvcName)
+        {
+            Grid1.GetCompute()
+                .ExecuteJavaTask<object>("org.apache.ignite.platform.PlatformDeployServiceTask", javaSvcName);
+
+            TestUtils.WaitForCondition(() => Services.GetServiceDescriptors().Any(x => x.Name == javaSvcName), 1000);
+        }
+
+        /// <summary>
         /// Tests the footer setting.
         /// </summary>
         [Test]
index adae2b1..d99b60e 100644 (file)
     <Compile Include="Impl\IPlatformTargetInternal.cs" />
     <Compile Include="Impl\DataRegionMetrics.cs" />
     <Compile Include="Impl\PersistentStore\PersistentStoreMetrics.cs" />
+    <Compile Include="Impl\Services\DynamicServiceProxy.cs" />
     <Compile Include="Impl\Services\ServiceMethodHelper.cs" />
     <Compile Include="Impl\Services\ServiceProxyFactory.cs" />
     <Compile Include="Impl\Services\ServiceProxyTypeGenerator.cs" />
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/DynamicServiceProxy.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/DynamicServiceProxy.cs
new file mode 100644 (file)
index 0000000..71e9768
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * 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;
+    using System.Diagnostics;
+    using System.Dynamic;
+
+    /// <summary>
+    /// Service proxy based on DynamicObject to be used with <c>dynamic</c> keyword.
+    /// </summary>
+    internal class DynamicServiceProxy : DynamicObject
+    {
+        private readonly Func<string, object[], object> _invokeMethod;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DynamicServiceProxy"/> class.
+        /// </summary>
+        /// <param name="invokeMethod">The service invoke method.</param>
+        public DynamicServiceProxy(Func<string, object[], object> invokeMethod)
+        {
+            Debug.Assert(invokeMethod != null);
+
+            _invokeMethod = invokeMethod;
+        }
+
+        /// <summary>
+        /// Provides the implementation for operations that get member values.
+        /// </summary>
+        /// <param name="binder">Provides information about the object that called the dynamic operation.
+        /// The binder.Name property provides the name of the member on which the dynamic operation is performed.
+        /// </param>
+        /// <param name="result">The result of the get operation.</param>
+        /// <returns>
+        /// true if the operation is successful; otherwise, false. If this method returns false,
+        /// the run-time binder of the language determines the behavior.
+        /// (In most cases, a run-time exception is thrown.)
+        /// </returns>
+        public override bool TryGetMember(GetMemberBinder binder, out object result)
+        {
+            // Note that we don't know whether it is a field or a property,
+            // but services are supposed to be accessed through an interface, so property is assumed.
+            result = _invokeMethod("get_" + binder.Name, null);
+            return true;
+        }
+
+        /// <summary>
+        /// Provides the implementation for operations that set member values.
+        /// </summary>
+        /// <param name="binder">Provides information about the object that called the dynamic operation
+        /// The binder.Name property provides the name of the member to which the value is being assigned. </param>
+        /// <param name="value">The value to set to the member.</param>
+        /// <returns>
+        /// true if the operation is successful; otherwise, false. If this method returns false,
+        /// the run-time binder of the language determines the behavior.
+        /// (In most cases, a language-specific run-time exception is thrown.)
+        /// </returns>
+        public override bool TrySetMember(SetMemberBinder binder, object value)
+        {
+            // Note that we don't know whether it is a field or a property,
+            // but services are supposed to be accessed through an interface, so property is assumed.
+            _invokeMethod("set_" + binder.Name, new[] { value });
+            return true;
+        }
+
+        /// <summary>
+        /// Provides the implementation for operations that invoke a member.
+        /// </summary>
+        /// <param name="binder">Provides information about the dynamic operation.
+        /// The binder.Name property provides the name of the member on which the dynamic operation is performed.
+        /// </param>
+        /// <param name="args">The arguments that are passed to the object member during the invoke operation.</param>
+        /// <param name="result">The result of the member invocation.</param>
+        /// <returns>
+        /// true if the operation is successful; otherwise, false.
+        /// If this method returns false, the run-time binder of the language determines the behavior.
+        /// (In most cases, a language-specific run-time exception is thrown.)
+        /// </returns>
+        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
+        {
+            result = _invokeMethod(binder.Name, args);
+            return true;
+        }
+
+        /// <summary>
+        /// Returns a <see cref="string" /> that represents this instance.
+        /// </summary>
+        public override string ToString()
+        {
+            return (string) _invokeMethod("ToString", null);
+        }
+    }
+}
index bf6cd16..006aa00 100644 (file)
@@ -69,13 +69,16 @@ namespace Apache.Ignite.Core.Impl.Services
         /// <summary>
         /// Finds suitable method in the specified type, or throws an exception.
         /// </summary>
-        private static Func<object, object[], object> GetMethodOrThrow(Type svcType, string methodName, object[] arguments)
+        private static Func<object, object[], object> GetMethodOrThrow(Type svcType, string methodName,
+            object[] arguments)
         {
             Debug.Assert(svcType != null);
             Debug.Assert(!string.IsNullOrWhiteSpace(methodName));
+            
+            var argsLength = arguments == null ? 0 : arguments.Length;
 
             // 0) Check cached methods
-            var cacheKey = Tuple.Create(svcType, methodName, arguments.Length);
+            var cacheKey = Tuple.Create(svcType, methodName, argsLength);
             Func<object, object[], object> res;
 
             if (Methods.TryGetValue(cacheKey, out res))
@@ -83,7 +86,7 @@ namespace Apache.Ignite.Core.Impl.Services
 
             // 1) Find methods by name
             var methods = svcType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
-                .Where(m => CleanupMethodName(m) == methodName && m.GetParameters().Length == arguments.Length)
+                .Where(m => CleanupMethodName(m) == methodName && m.GetParameters().Length == argsLength)
                 .ToArray();
 
             if (methods.Length == 1)
@@ -96,7 +99,7 @@ namespace Apache.Ignite.Core.Impl.Services
                 throw new InvalidOperationException(
                     string.Format(CultureInfo.InvariantCulture,
                         "Failed to invoke proxy: there is no method '{0}' in type '{1}' with {2} arguments", 
-                        methodName, svcType, arguments.Length));
+                        methodName, svcType, argsLength));
 
             // 2) There is more than 1 method with specified name - resolve with argument types.
             methods = methods.Where(m => AreMethodArgsCompatible(arguments, m.GetParameters())).ToArray();
@@ -105,10 +108,11 @@ namespace Apache.Ignite.Core.Impl.Services
                 return (obj, args) => methods[0].Invoke(obj, args);
 
             // 3) 0 or more than 1 matching method - throw.
-            var argsString = arguments.Length == 0
+            var argsString = argsLength == 0
                 ? "0"
                 : "(" +
                   // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+                  // ReSharper disable once AssignNullToNotNullAttribute
                   arguments.Select(x => x == null ? "null" : x.GetType().Name).Aggregate((x, y) => x + ", " + y)
                   + ")";
 
index 422908f..42638bc 100644 (file)
@@ -37,16 +37,16 @@ namespace Apache.Ignite.Core.Impl.Services
         /// Writes proxy method invocation data to the specified writer.
         /// </summary>
         /// <param name="writer">Writer.</param>
-        /// <param name="method">Method.</param>
+        /// <param name="methodName">Name of the method.</param>
+        /// <param name="method">Method (optional, can be null).</param>
         /// <param name="arguments">Arguments.</param>
         /// <param name="platform">The platform.</param>
-        public static void WriteProxyMethod(BinaryWriter writer, MethodBase method, object[] arguments, 
-            Platform platform)
+        public static void WriteProxyMethod(BinaryWriter writer, string methodName, MethodBase method,
+            object[] arguments, Platform platform)
         {
             Debug.Assert(writer != null);
-            Debug.Assert(method != null);
 
-            writer.WriteString(method.Name);
+            writer.WriteString(methodName);
 
             if (arguments != null)
             {
@@ -55,7 +55,7 @@ namespace Apache.Ignite.Core.Impl.Services
 
                 if (platform == Platform.DotNet)
                 {
-                    // Write as is
+                    // Write as is for .NET.
                     foreach (var arg in arguments)
                     {
                         writer.WriteObjectDetached(arg);
@@ -64,11 +64,13 @@ namespace Apache.Ignite.Core.Impl.Services
                 else
                 {
                     // Other platforms do not support Serializable, need to convert arrays and collections
-                    var methodArgs = method.GetParameters();
-                    Debug.Assert(methodArgs.Length == arguments.Length);
+                    var mParams = method != null ? method.GetParameters() : null;
+                    Debug.Assert(mParams == null || mParams.Length == arguments.Length);
 
-                    for (int i = 0; i < arguments.Length; i++)
-                        WriteArgForPlatforms(writer, methodArgs[i], arguments[i]);
+                    for (var i = 0; i < arguments.Length; i++)
+                    {
+                        WriteArgForPlatforms(writer, mParams != null ? mParams[i].ParameterType : null, arguments[i]);
+                    }
                 }
             }
             else
@@ -213,9 +215,9 @@ namespace Apache.Ignite.Core.Impl.Services
         /// <summary>
         /// Writes the argument in platform-compatible format.
         /// </summary>
-        private static void WriteArgForPlatforms(BinaryWriter writer, ParameterInfo param, object arg)
+        private static void WriteArgForPlatforms(BinaryWriter writer, Type paramType, object arg)
         {
-            var hnd = GetPlatformArgWriter(param, arg);
+            var hnd = GetPlatformArgWriter(paramType, arg);
 
             if (hnd != null)
             {
@@ -230,14 +232,19 @@ namespace Apache.Ignite.Core.Impl.Services
         /// <summary>
         /// Gets arg writer for platform-compatible service calls.
         /// </summary>
-        private static Action<BinaryWriter, object> GetPlatformArgWriter(ParameterInfo param, object arg)
+        private static Action<BinaryWriter, object> GetPlatformArgWriter(Type paramType, object arg)
         {
-            var type = param.ParameterType;
+            if (arg == null)
+            {
+                return null;
+            }
+
+            var type = paramType ?? arg.GetType();
 
             // Unwrap nullable
             type = Nullable.GetUnderlyingType(type) ?? type;
 
-            if (arg == null || type.IsPrimitive)
+            if (type.IsPrimitive)
                 return null;
 
             var handler = BinarySystemHandlers.GetWriteHandler(type);
index 0360f97..53f9621 100644 (file)
@@ -367,7 +367,8 @@ namespace Apache.Ignite.Core.Impl.Services
         public T GetServiceProxy<T>(string name, bool sticky) where T : class
         {
             IgniteArgumentCheck.NotNullOrEmpty(name, "name");
-            IgniteArgumentCheck.Ensure(typeof(T).IsInterface, "T", "Service proxy type should be an interface: " + typeof(T));
+            IgniteArgumentCheck.Ensure(typeof(T).IsInterface, "T", 
+                "Service proxy type should be an interface: " + typeof(T));
 
             // 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
@@ -385,24 +386,56 @@ namespace Apache.Ignite.Core.Impl.Services
             var platform = GetServiceDescriptors().Cast<ServiceDescriptor>().Single(x => x.Name == name).Platform;
 
             return ServiceProxyFactory<T>.CreateProxy((method, args) =>
-                InvokeProxyMethod(javaProxy, method, args, platform));
+                InvokeProxyMethod(javaProxy, method.Name, method, args, platform));
+        }
+
+        /** <inheritDoc /> */
+        public dynamic GetDynamicServiceProxy(string name)
+        {
+            return GetDynamicServiceProxy(name, false);
+        }
+
+        /** <inheritDoc /> */
+        public dynamic GetDynamicServiceProxy(string name, bool sticky)
+        {
+            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)
+            {
+                return locInst;
+            }
+
+            var javaProxy = DoOutOpObject(OpServiceProxy, w =>
+            {
+                w.WriteString(name);
+                w.WriteBoolean(sticky);
+            });
+
+            var platform = GetServiceDescriptors().Cast<ServiceDescriptor>().Single(x => x.Name == name).Platform;
+
+            return new DynamicServiceProxy((methodName, args) =>
+                InvokeProxyMethod(javaProxy, methodName, null, args, platform));
         }
 
         /// <summary>
         /// Invokes the service proxy method.
         /// </summary>
         /// <param name="proxy">Unmanaged proxy.</param>
+        /// <param name="methodName">Name of the method.</param>
         /// <param name="method">Method to invoke.</param>
         /// <param name="args">Arguments.</param>
         /// <param name="platform">The platform.</param>
         /// <returns>
         /// Invocation result.
         /// </returns>
-        private object InvokeProxyMethod(IPlatformTargetInternal proxy, MethodBase method, object[] args, 
-            Platform platform)
+        private object InvokeProxyMethod(IPlatformTargetInternal proxy, string methodName,
+            MethodBase method, object[] args, Platform platform)
         {
             return DoOutInOp(OpInvokeMethod,
-                writer => ServiceProxySerializer.WriteProxyMethod(writer, method, args, platform),
+                writer => ServiceProxySerializer.WriteProxyMethod(writer, methodName, method, args, platform),
                 (stream, res) => ServiceProxySerializer.ReadInvocationResult(stream, Marshaller, _keepBinary), 
                 proxy);
         }
index bcbd4fb..84c23fa 100644 (file)
@@ -272,6 +272,32 @@ namespace Apache.Ignite.Core.Services
         T GetServiceProxy<T>(string name, bool sticky) 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.
+        /// <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>
+        /// <returns>Either proxy over remote service or local service if it is deployed locally.</returns>
+        dynamic GetDynamicServiceProxy(string name);
+
+        /// <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.
+        /// <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>
+        /// <returns>Either proxy over remote service or local service if it is deployed locally.</returns>
+        dynamic GetDynamicServiceProxy(string name, bool sticky);
+
+        /// <summary>
         /// Returns an instance with binary mode enabled.
         /// Service method results will be kept in binary form.
         /// </summary>