IGNITE-7561 .NET: Add IServices.GetDynamicServiceProxy

This closes #3457


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

Branch: refs/heads/ignite-7485-2
Commit: f8d16749c14089b4fba840a2e424e411763a52da
Parents: 6ae7014
Author: Pavel Tupitsyn <ptupit...@apache.org>
Authored: Thu Feb 1 14:53:16 2018 +0300
Committer: Pavel Tupitsyn <ptupit...@apache.org>
Committed: Thu Feb 1 14:53:16 2018 +0300

----------------------------------------------------------------------
 .../Services/ServiceProxyTest.cs                |   3 +-
 .../Services/ServicesAsyncWrapper.cs            |  18 ++-
 .../Services/ServicesTest.cs                    | 148 ++++++++++++++++++-
 .../Apache.Ignite.Core.csproj                   |   1 +
 .../Impl/Services/DynamicServiceProxy.cs        | 108 ++++++++++++++
 .../Impl/Services/ServiceProxyInvoker.cs        |  14 +-
 .../Impl/Services/ServiceProxySerializer.cs     |  37 +++--
 .../Impl/Services/Services.cs                   |  43 +++++-
 .../Apache.Ignite.Core/Services/IServices.cs    |  26 ++++
 9 files changed, 363 insertions(+), 35 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs
----------------------------------------------------------------------
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 6146146..cb36d1d 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs
@@ -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();
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs
----------------------------------------------------------------------
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 18db5d5..cf058a9 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs
@@ -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());

http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
----------------------------------------------------------------------
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 b8f4cdf..c6a8e0b 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
@@ -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]

http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj 
b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
index adae2b1..d99b60e 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
@@ -103,6 +103,7 @@
     <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" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/DynamicServiceProxy.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
index 0000000..71e9768
--- /dev/null
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/DynamicServiceProxy.cs
@@ -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);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs
index bf6cd16..006aa00 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs
@@ -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)
                   + ")";
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs
----------------------------------------------------------------------
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 422908f..42638bc 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs
@@ -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);

http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs
----------------------------------------------------------------------
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 0360f97..53f9621 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs
@@ -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);
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs 
b/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs
index bcbd4fb..84c23fa 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs
@@ -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>

Reply via email to