You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vo...@apache.org on 2015/09/22 09:19:27 UTC

[25/37] ignite git commit: IGNITE-1513: WIP on .Net.

http://git-wip-us.apache.org/repos/asf/ignite/blob/65bb69da/modules/platform/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcess.cs
----------------------------------------------------------------------
diff --git a/modules/platform/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcess.cs b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcess.cs
new file mode 100644
index 0000000..fd08116
--- /dev/null
+++ b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcess.cs
@@ -0,0 +1,283 @@
+/*
+ * 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.Tests.Process
+{
+    using System;
+    using System.Diagnostics;
+    using System.IO;
+    using System.Linq;
+    using System.Text;
+    using System.Threading;
+    using Apache.Ignite.Core.Impl;
+
+    /// <summary>
+    /// Defines forked Ignite node.
+    /// </summary>
+    public class IgniteProcess
+    {
+        /** Executable file name. */
+        private static readonly string ExeName = "Apache.Ignite.exe";
+
+        /** Executable process name. */
+        private static readonly string ExeProcName = ExeName.Substring(0, ExeName.LastIndexOf('.'));
+
+        /** Executable configuration file name. */
+        private static readonly string ExeCfgName = ExeName + ".config";
+
+        /** Executable backup configuration file name. */
+        private static readonly string ExeCfgBakName = ExeCfgName + ".bak";
+
+        /** Directory where binaries are stored. */
+        private static readonly string ExeDir;
+
+        /** Full path to executable. */
+        private static readonly string ExePath;
+
+        /** Full path to executable configuration file. */
+        private static readonly string ExeCfgPath;
+
+        /** Full path to executable configuration file backup. */
+        private static readonly string ExeCfgBakPath;
+
+        /** Default process output reader. */
+        private static readonly IIgniteProcessOutputReader DfltOutReader = new IgniteProcessConsoleOutputReader();
+
+        /** Process. */
+        private readonly Process _proc;
+
+        /// <summary>
+        /// Static initializer.
+        /// </summary>
+        static IgniteProcess()
+        {
+            // 1. Locate executable file and related stuff.
+            DirectoryInfo dir = new FileInfo(new Uri(typeof(IgniteProcess).Assembly.CodeBase).LocalPath).Directory;
+
+            // ReSharper disable once PossibleNullReferenceException
+            ExeDir = dir.FullName;
+
+            var exe = dir.GetFiles(ExeName);
+
+            if (exe.Length == 0)
+                throw new Exception(ExeName + " is not found in test output directory: " + dir.FullName);
+
+            ExePath = exe[0].FullName;
+
+            var exeCfg = dir.GetFiles(ExeCfgName);
+
+            if (exeCfg.Length == 0)
+                throw new Exception(ExeCfgName + " is not found in test output directory: " + dir.FullName);
+
+            ExeCfgPath = exeCfg[0].FullName;
+
+            ExeCfgBakPath = Path.Combine(ExeDir, ExeCfgBakName);
+
+            File.Delete(ExeCfgBakPath);
+        }
+
+        /// <summary>
+        /// Save current configuration to backup.
+        /// </summary>
+        public static void SaveConfigurationBackup()
+        {
+            File.Copy(ExeCfgPath, ExeCfgBakPath, true);
+        }
+
+        /// <summary>
+        /// Restore configuration from backup.
+        /// </summary>
+        public static void RestoreConfigurationBackup()
+        {
+            File.Copy(ExeCfgBakPath, ExeCfgPath, true);
+        }
+
+        /// <summary>
+        /// Replace application configuration with another one.
+        /// </summary>
+        /// <param name="relPath">Path to config relative to executable directory.</param>
+        public static void ReplaceConfiguration(string relPath)
+        {
+            File.Copy(Path.Combine(ExeDir, relPath), ExeCfgPath, true);
+        }
+
+        /// <summary>
+        /// Kill all Ignite processes.
+        /// </summary>
+        public static void KillAll()
+        {
+            foreach (Process proc in Process.GetProcesses())
+            {
+                if (proc.ProcessName.Equals(ExeProcName))
+                {
+                    proc.Kill();
+
+                    proc.WaitForExit();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Construector.
+        /// </summary>
+        /// <param name="args">Arguments</param>
+        public IgniteProcess(params string[] args) : this(DfltOutReader, args) { }
+
+        /// <summary>
+        /// Construector.
+        /// </summary>
+        /// <param name="outReader">Output reader.</param>
+        /// <param name="args">Arguments.</param>
+        public IgniteProcess(IIgniteProcessOutputReader outReader, params string[] args)
+        {
+            // Add test dll path
+            args = args.Concat(new[] {"-assembly=" + GetType().Assembly.Location}).ToArray();
+
+            _proc = Start(ExePath, IgniteManager.GetIgniteHome(null), outReader, args);
+        }
+
+        /// <summary>
+        /// Starts a grid process.
+        /// </summary>
+        /// <param name="exePath">Exe path.</param>
+        /// <param name="ggHome">Ignite home.</param>
+        /// <param name="outReader">Output reader.</param>
+        /// <param name="args">Arguments.</param>
+        /// <returns>Started process.</returns>
+        public static Process Start(string exePath, string ggHome, IIgniteProcessOutputReader outReader = null, 
+            params string[] args)
+        {
+            Debug.Assert(!string.IsNullOrEmpty(exePath));
+            Debug.Assert(!string.IsNullOrEmpty(ggHome));
+
+            // 1. Define process start configuration.
+            var sb = new StringBuilder();
+
+            foreach (string arg in args)
+                sb.Append('\"').Append(arg).Append("\" ");
+
+            var procStart = new ProcessStartInfo
+            {
+                FileName = exePath,
+                Arguments = sb.ToString()
+            };
+
+            if (!string.IsNullOrEmpty(ggHome))
+                procStart.EnvironmentVariables[IgniteManager.EnvIgniteHome] = ggHome;
+
+            procStart.EnvironmentVariables[IgniteManager.EnvIgniteNativeTestClasspath] = "true";
+
+            procStart.CreateNoWindow = true;
+            procStart.UseShellExecute = false;
+
+            procStart.RedirectStandardOutput = true;
+            procStart.RedirectStandardError = true;
+
+            var workDir = Path.GetDirectoryName(exePath);
+
+            if (workDir != null)
+                procStart.WorkingDirectory = workDir;
+
+            Console.WriteLine("About to run Apache.Ignite.exe process [exePath=" + exePath + ", arguments=" + sb + ']');
+
+            // 2. Start.
+            var proc = Process.Start(procStart);
+
+            Debug.Assert(proc != null);
+
+            // 3. Attach output readers to avoid hangs.
+            outReader = outReader ?? DfltOutReader;
+
+            Attach(proc, proc.StandardOutput, outReader, false);
+            Attach(proc, proc.StandardError, outReader, true);
+
+            return proc;
+        }
+
+        /// <summary>
+        /// Whether the process is still alive.
+        /// </summary>
+        public bool Alive
+        {
+            get { return !_proc.HasExited; }
+        }
+
+        /// <summary>
+        /// Kill process.
+        /// </summary>
+        public void Kill()
+        {
+            _proc.Kill();
+        }
+
+        /// <summary>
+        /// Join process.
+        /// </summary>
+        /// <returns>Exit code.</returns>
+        public int Join()
+        {
+            _proc.WaitForExit();
+
+            return _proc.ExitCode;
+        }
+
+        /// <summary>
+        /// Join process with timeout.
+        /// </summary>
+        /// <param name="timeout">Timeout in milliseconds.</param>
+        /// <returns><c>True</c> if process exit occurred before timeout.</returns>
+        public bool Join(int timeout)
+        {
+            return _proc.WaitForExit(timeout);
+        }
+
+        /// <summary>
+        /// Join process with timeout.
+        /// </summary>
+        /// <param name="timeout">Timeout in milliseconds.</param>
+        /// <param name="exitCode">Exit code.</param>
+        /// <returns><c>True</c> if process exit occurred before timeout.</returns>
+        public bool Join(int timeout, out int exitCode)
+        {
+            if (_proc.WaitForExit(timeout))
+            {
+                exitCode = _proc.ExitCode;
+
+                return true;
+            }
+            exitCode = 0;
+
+            return false;
+        }
+
+        /// <summary>
+        /// Attach output reader to the process.
+        /// </summary>
+        /// <param name="proc">Process.</param>
+        /// <param name="reader">Process stream reader.</param>
+        /// <param name="outReader">Output reader.</param>
+        /// <param name="err">Whether this is error stream.</param>
+        private static void Attach(Process proc, StreamReader reader, IIgniteProcessOutputReader outReader, bool err)
+        {
+            new Thread(() =>
+            {
+                while (!proc.HasExited)
+                    outReader.OnOutput(proc, reader.ReadLine(), err);
+            }) {IsBackground = true}.Start();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/65bb69da/modules/platform/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcessConsoleOutputReader.cs
----------------------------------------------------------------------
diff --git a/modules/platform/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcessConsoleOutputReader.cs b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcessConsoleOutputReader.cs
new file mode 100644
index 0000000..00cc040
--- /dev/null
+++ b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Process/IgniteProcessConsoleOutputReader.cs
@@ -0,0 +1,40 @@
+/*
+ * 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.Tests.Process
+{
+    using System;
+    using System.Diagnostics;
+
+    /// <summary>
+    /// Output reader pushing data to the console.
+    /// </summary>
+    public class IgniteProcessConsoleOutputReader : IIgniteProcessOutputReader
+    {
+        /** Out message format. */
+        private static readonly string OutFormat = ">>> {0} OUT: {1}";
+
+        /** Error message format. */
+        private static readonly string ErrFormat = ">>> {0} ERR: {1}";
+
+        /** <inheritDoc /> */
+        public void OnOutput(Process proc, string data, bool err)
+        {
+            Console.WriteLine(err ? ErrFormat : OutFormat, proc.Id, data);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/65bb69da/modules/platform/dotnet/Apache.Ignite.Core.Tests/Properties/AssemblyInfo.cs
----------------------------------------------------------------------
diff --git a/modules/platform/dotnet/Apache.Ignite.Core.Tests/Properties/AssemblyInfo.cs b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..1ebcf24
--- /dev/null
+++ b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Apache.Ignite.Core.Tests")]
+[assembly: AssemblyDescription("Apache Ignite .NET Core Tests")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Apache Software Foundation")]
+[assembly: AssemblyProduct("Apache.Ignite.Core.Tests")]
+[assembly: AssemblyCopyright("Copyright ©  2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+
+[assembly: Guid("de8dd5cc-7c7f-4a09-80d5-7086d9416a7b")]
+
+[assembly: AssemblyVersion("1.5.0")]
+[assembly: AssemblyFileVersion("1.5.0")]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/65bb69da/modules/platform/dotnet/Apache.Ignite.Core.Tests/Query/ImplicitPortablePerson.cs
----------------------------------------------------------------------
diff --git a/modules/platform/dotnet/Apache.Ignite.Core.Tests/Query/ImplicitPortablePerson.cs b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Query/ImplicitPortablePerson.cs
new file mode 100644
index 0000000..f80c4eb
--- /dev/null
+++ b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Query/ImplicitPortablePerson.cs
@@ -0,0 +1,46 @@
+/*
+ * 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.Tests.Query
+{
+    /// <summary>
+    /// Test person.
+    /// </summary>
+    internal class ImplicitPortablePerson
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ImplicitPortablePerson"/> class.
+        /// </summary>
+        /// <param name="name">The name.</param>
+        /// <param name="age">The age.</param>
+        public ImplicitPortablePerson(string name, int age)
+        {
+            Name = name;
+            Age = age;
+        }
+
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the age.
+        /// </summary>
+        public int Age { get; set; }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/65bb69da/modules/platform/dotnet/Apache.Ignite.Core.Tests/Query/NoDefPortablePerson.cs
----------------------------------------------------------------------
diff --git a/modules/platform/dotnet/Apache.Ignite.Core.Tests/Query/NoDefPortablePerson.cs b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Query/NoDefPortablePerson.cs
new file mode 100644
index 0000000..16bd07d
--- /dev/null
+++ b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Query/NoDefPortablePerson.cs
@@ -0,0 +1,35 @@
+/*
+ * 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.Tests.Query
+{
+    /// <summary>
+    /// Test person.
+    /// </summary>
+    internal class NoDefPortablePerson
+    {
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the age.
+        /// </summary>
+        public int Age { get; set; }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/65bb69da/modules/platform/dotnet/Apache.Ignite.Core.Tests/Query/PortablePerson.cs
----------------------------------------------------------------------
diff --git a/modules/platform/dotnet/Apache.Ignite.Core.Tests/Query/PortablePerson.cs b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Query/PortablePerson.cs
new file mode 100644
index 0000000..1e11001
--- /dev/null
+++ b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Query/PortablePerson.cs
@@ -0,0 +1,69 @@
+/*
+ * 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.Tests.Query
+{
+    using Apache.Ignite.Core.Portable;
+
+    /// <summary>
+    /// Test person.
+    /// </summary>
+    internal class PortablePerson : IPortableMarshalAware
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="PortablePerson"/> class.
+        /// </summary>
+        /// <param name="name">The name.</param>
+        /// <param name="age">The age.</param>
+        public PortablePerson(string name, int age)
+        {
+            Name = name;
+            Age = age;
+        }
+
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the address.
+        /// </summary>
+        public string Address { get; set; }
+
+        /// <summary>
+        /// Gets or sets the age.
+        /// </summary>
+        public int Age { get; set; }
+
+        /** <ineritdoc /> */
+        public void WritePortable(IPortableWriter writer)
+        {
+            writer.WriteString("name", Name);
+            writer.WriteString("address", Address);
+            writer.WriteInt("age", Age);
+        }
+
+        /** <ineritdoc /> */
+        public void ReadPortable(IPortableReader reader)
+        {
+            Name = reader.ReadString("name");
+            Address = reader.ReadString("address");
+            Age = reader.ReadInt("age");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/65bb69da/modules/platform/dotnet/Apache.Ignite.Core.Tests/SerializationTest.cs
----------------------------------------------------------------------
diff --git a/modules/platform/dotnet/Apache.Ignite.Core.Tests/SerializationTest.cs b/modules/platform/dotnet/Apache.Ignite.Core.Tests/SerializationTest.cs
new file mode 100644
index 0000000..e1a543e
--- /dev/null
+++ b/modules/platform/dotnet/Apache.Ignite.Core.Tests/SerializationTest.cs
@@ -0,0 +1,240 @@
+/*
+ * 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.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Reflection;
+    using System.Reflection.Emit;
+    using System.Runtime.Serialization;
+    using System.Xml;
+    using Apache.Ignite.Core.Cluster;
+    using Apache.Ignite.Core.Compute;
+    using Apache.Ignite.Core.Impl;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests for native serialization.
+    /// </summary>
+    public class SerializationTest
+    {
+        /** Grid name. */
+        private const string GridName = "SerializationTest";
+
+        /// <summary>
+        /// Set up routine.
+        /// </summary>
+        [TestFixtureSetUp]
+        public void SetUp()
+        {
+            var cfg = new IgniteConfigurationEx
+            {
+                GridName = GridName,
+                JvmClasspath = TestUtils.CreateTestClasspath(),
+                JvmOptions = TestUtils.TestJavaOptions(),
+                SpringConfigUrl = "config\\native-client-test-cache.xml"
+            };
+
+            Ignition.Start(cfg);
+        }
+
+        /// <summary>
+        /// Tear down routine.
+        /// </summary>
+        [TestFixtureTearDown]
+        public void TearDown()
+        {
+            Ignition.StopAll(true);
+        }
+
+        /// <summary>
+        /// Test complex file serialization.
+        /// </summary>
+        [Test]
+        public void TestSerializableXmlDoc()
+        {
+            var grid = Ignition.GetIgnite(GridName);
+            var cache = grid.GetCache<int, SerializableXmlDoc>("replicated");
+
+            var doc = new SerializableXmlDoc();
+
+            doc.LoadXml("<document><test1>val</test1><test2 attr=\"x\" /></document>");
+
+            for (var i = 0; i < 50; i++)
+            {
+                // Test cache
+                cache.Put(i, doc);
+
+                var resultDoc = cache.Get(i);
+
+                Assert.AreEqual(doc.OuterXml, resultDoc.OuterXml);
+
+                // Test task with document arg
+                CheckTask(grid, doc);
+            }
+        }
+
+        /// <summary>
+        /// Checks task execution.
+        /// </summary>
+        /// <param name="grid">Grid.</param>
+        /// <param name="arg">Task arg.</param>
+        private static void CheckTask(IIgnite grid, object arg)
+        {
+            var jobResult = grid.GetCompute().Execute(new CombineStringsTask(), arg);
+
+            var nodeCount = grid.GetCluster().GetNodes().Count;
+
+            var expectedRes =
+                CombineStringsTask.CombineStrings(Enumerable.Range(0, nodeCount).Select(x => arg.ToString()));
+
+            Assert.AreEqual(expectedRes, jobResult.InnerXml);
+        }
+
+        /// <summary>
+        /// Tests custom serialization binder.
+        /// </summary>
+        [Test]
+        public void TestSerializationBinder()
+        {
+            const int count = 50;
+
+            var cache = Ignition.GetIgnite(GridName).GetCache<int, object>("local");
+
+            // Put multiple objects from muliple same-named assemblies to cache
+            for (var i = 0; i < count; i++)
+            {
+                dynamic val = Activator.CreateInstance(GenerateDynamicType());
+                
+                val.Id = i;
+                val.Name = "Name_" + i;
+
+                cache.Put(i, val);
+            }
+
+            // Verify correct deserialization
+            for (var i = 0; i < count; i++)
+            {
+                dynamic val = cache.Get(i);
+
+                Assert.AreEqual(val.Id, i);
+                Assert.AreEqual(val.Name, "Name_" + i);
+            }
+        }
+
+        /// <summary>
+        /// Generates a Type in runtime, puts it into a dynamic assembly.
+        /// </summary>
+        /// <returns></returns>
+        public static Type GenerateDynamicType()
+        {
+            var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
+                new AssemblyName("GridSerializationTestDynamicAssembly"), AssemblyBuilderAccess.Run);
+
+            var moduleBuilder = asmBuilder.DefineDynamicModule("GridSerializationTestDynamicModule");
+
+            var typeBuilder = moduleBuilder.DefineType("GridSerializationTestDynamicType",
+                TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Serializable);
+
+            typeBuilder.DefineField("Id", typeof (int), FieldAttributes.Public);
+            
+            typeBuilder.DefineField("Name", typeof (string), FieldAttributes.Public);
+
+            return typeBuilder.CreateType();
+        }
+    }
+
+    [Serializable]
+    [DataContract]
+    public sealed class SerializableXmlDoc : XmlDocument, ISerializable
+    {
+        /// <summary>
+        /// Default ctor.
+        /// </summary>
+        public SerializableXmlDoc()
+        {
+            // No-op
+        }
+
+        /// <summary>
+        /// Serialization ctor.
+        /// </summary>
+        private SerializableXmlDoc(SerializationInfo info, StreamingContext context)
+        {
+            LoadXml(info.GetString("xmlDocument"));
+        }
+
+        /** <inheritdoc /> */
+        public void GetObjectData(SerializationInfo info, StreamingContext context)
+        {
+            info.AddValue("xmlDocument", OuterXml, typeof(string));
+        }
+    }
+
+    [Serializable]
+    public class CombineStringsTask : IComputeTask<object, string, SerializableXmlDoc>
+    {
+        public IDictionary<IComputeJob<string>, IClusterNode> Map(IList<IClusterNode> subgrid, object arg)
+        {
+            return subgrid.ToDictionary(x => (IComputeJob<string>) new ToStringJob {Arg = arg}, x => x);
+        }
+
+        public ComputeJobResultPolicy Result(IComputeJobResult<string> res, IList<IComputeJobResult<string>> rcvd)
+        {
+            return ComputeJobResultPolicy.Wait;
+        }
+
+        public SerializableXmlDoc Reduce(IList<IComputeJobResult<string>> results)
+        {
+            var result = new SerializableXmlDoc();
+
+            result.LoadXml(CombineStrings(results.Select(x => x.Data())));
+
+            return result;
+        }
+
+        public static string CombineStrings(IEnumerable<string> strings)
+        {
+            var text = string.Concat(strings.Select(x => string.Format("<val>{0}</val>", x)));
+
+            return string.Format("<document>{0}</document>", text);
+        }
+    }
+
+    [Serializable]
+    public class ToStringJob : IComputeJob<string>
+    {
+        /// <summary>
+        /// Job argument.
+        /// </summary>
+        public object Arg { get; set; }
+
+        /** <inheritdoc /> */
+        public string Execute()
+        {
+            return Arg.ToString();
+        }
+
+        /** <inheritdoc /> */
+        public void Cancel()
+        {
+            // No-op.
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/65bb69da/modules/platform/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs
----------------------------------------------------------------------
diff --git a/modules/platform/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs
new file mode 100644
index 0000000..44e1d71
--- /dev/null
+++ b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs
@@ -0,0 +1,741 @@
+/*
+ * 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.Tests.Services
+{
+    using System;
+    using System.Diagnostics.CodeAnalysis;
+    using System.IO;
+    using System.Linq;
+    using System.Reflection;
+    using Apache.Ignite.Core.Impl.Memory;
+    using Apache.Ignite.Core.Impl.Portable;
+    using Apache.Ignite.Core.Impl.Services;
+    using Apache.Ignite.Core.Portable;
+    using Apache.Ignite.Core.Services;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests <see cref="ServiceProxySerializer"/> functionality.
+    /// </summary>
+    public class ServiceProxyTest
+    {
+        /** */
+        private TestIgniteService _svc;
+
+        /** */
+        private readonly PortableMarshaller _marsh = new PortableMarshaller(new PortableConfiguration
+        {
+            TypeConfigurations = new[]
+            {
+                new PortableTypeConfiguration(typeof (TestPortableClass)),
+                new PortableTypeConfiguration(typeof (CustomExceptionPortable))
+            }
+        });
+
+        /** */
+        protected readonly IPortables Portables;
+
+        /** */
+        private readonly PlatformMemoryManager _memory = new PlatformMemoryManager(1024);
+
+        /** */
+        protected bool KeepPortable;
+
+        /** */
+        protected bool SrvKeepPortable;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ServiceProxyTest"/> class.
+        /// </summary>
+        public ServiceProxyTest()
+        {
+            Portables = new PortablesImpl(_marsh);
+        }
+
+        /// <summary>
+        /// Tests object class methods proxying.
+        /// </summary>
+        [Test]
+        public void TestObjectClassMethods()
+        {
+            var prx = GetProxy();
+
+            prx.IntProp = 12345;
+
+            Assert.AreEqual("12345", prx.ToString());
+            Assert.AreEqual("12345", _svc.ToString());
+            Assert.AreEqual(12345, prx.GetHashCode());
+            Assert.AreEqual(12345, _svc.GetHashCode());
+        }
+
+        /// <summary>
+        /// Tests properties proxying.
+        /// </summary>
+        [Test]
+        [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
+        public void TestProperties()
+        {
+            var prx = GetProxy();
+
+            prx.IntProp = 10;
+            Assert.AreEqual(10, prx.IntProp);
+            Assert.AreEqual(10, _svc.IntProp);
+
+            _svc.IntProp = 15;
+            Assert.AreEqual(15, prx.IntProp);
+            Assert.AreEqual(15, _svc.IntProp);
+
+            prx.ObjProp = "prop1";
+            Assert.AreEqual("prop1", prx.ObjProp);
+            Assert.AreEqual("prop1", _svc.ObjProp);
+
+            prx.ObjProp = null;
+            Assert.IsNull(prx.ObjProp);
+            Assert.IsNull(_svc.ObjProp);
+
+            prx.ObjProp = new TestClass {Prop = "prop2"};
+            Assert.AreEqual("prop2", ((TestClass)prx.ObjProp).Prop);
+            Assert.AreEqual("prop2", ((TestClass)_svc.ObjProp).Prop);
+        }
+
+        /// <summary>
+        /// Tests void methods proxying.
+        /// </summary>
+        [Test]
+        public void TestVoidMethods()
+        {
+            var prx = GetProxy();
+
+            prx.VoidMethod();
+            Assert.AreEqual("VoidMethod", prx.InvokeResult);
+            Assert.AreEqual("VoidMethod", _svc.InvokeResult);
+
+            prx.VoidMethod(10);
+            Assert.AreEqual(_svc.InvokeResult, prx.InvokeResult);
+
+            prx.VoidMethod(10, "string");
+            Assert.AreEqual(_svc.InvokeResult, prx.InvokeResult);
+
+            prx.VoidMethod(10, "string", "arg");
+            Assert.AreEqual(_svc.InvokeResult, prx.InvokeResult);
+
+            prx.VoidMethod(10, "string", "arg", "arg1", 2, 3, "arg4");
+            Assert.AreEqual(_svc.InvokeResult, prx.InvokeResult);
+        }
+
+        /// <summary>
+        /// Tests object methods proxying.
+        /// </summary>
+        [Test]
+        public void TestObjectMethods()
+        {
+            var prx = GetProxy();
+
+            Assert.AreEqual("ObjectMethod", prx.ObjectMethod());
+            Assert.AreEqual("ObjectMethod987", prx.ObjectMethod(987));
+            Assert.AreEqual("ObjectMethod987str123", prx.ObjectMethod(987, "str123"));
+            Assert.AreEqual("ObjectMethod987str123TestClass", prx.ObjectMethod(987, "str123", new TestClass()));
+            Assert.AreEqual("ObjectMethod987str123TestClass34arg5arg6",
+                prx.ObjectMethod(987, "str123", new TestClass(), 3, 4, "arg5", "arg6"));
+        }
+
+        /// <summary>
+        /// Tests methods that exist in proxy interface, but do not exist in the actual service.
+        /// </summary>
+        [Test]
+        public void TestMissingMethods()
+        {
+            var prx = GetProxy();
+
+            var ex = Assert.Throws<InvalidOperationException>(() => prx.MissingMethod());
+
+            Assert.AreEqual("Failed to invoke proxy: there is no method 'MissingMethod'" +
+                            " in type 'Apache.Ignite.Core.Tests.Services.ServiceProxyTest+TestIgniteService'", ex.Message);
+        }
+
+        /// <summary>
+        /// Tests ambiguous methods handling (multiple methods with the same signature).
+        /// </summary>
+        [Test]
+        public void TestAmbiguousMethods()
+        {
+            var prx = GetProxy();
+
+            var ex = Assert.Throws<InvalidOperationException>(() => prx.AmbiguousMethod(1));
+
+            Assert.AreEqual("Failed to invoke proxy: there are 2 methods 'AmbiguousMethod' in type " +
+                            "'Apache.Ignite.Core.Tests.Services.ServiceProxyTest+TestIgniteService' with (Int32) arguments, " +
+                            "can't resolve ambiguity.", ex.Message);
+        }
+
+        [Test]
+        public void TestException()
+        {
+            var prx = GetProxy();
+
+            var err = Assert.Throws<ServiceInvocationException>(prx.ExceptionMethod);
+            Assert.AreEqual("Expected exception", err.InnerException.Message);
+
+            var ex = Assert.Throws<ServiceInvocationException>(() => prx.CustomExceptionMethod());
+            Assert.IsTrue(ex.ToString().Contains("+CustomException"));
+        }
+
+        [Test]
+        public void TestPortableMarshallingException()
+        {
+            var prx = GetProxy();
+                
+            var ex = Assert.Throws<ServiceInvocationException>(() => prx.CustomExceptionPortableMethod(false, false));
+
+            if (KeepPortable)
+            {
+                Assert.AreEqual("Proxy method invocation failed with a portable error. " +
+                                "Examine PortableCause for details.", ex.Message);
+
+                Assert.IsNotNull(ex.PortableCause);
+                Assert.IsNull(ex.InnerException);
+            }
+            else
+            {
+                Assert.AreEqual("Proxy method invocation failed with an exception. " +
+                                "Examine InnerException for details.", ex.Message);
+
+                Assert.IsNull(ex.PortableCause);
+                Assert.IsNotNull(ex.InnerException);
+            }
+
+            ex = Assert.Throws<ServiceInvocationException>(() => prx.CustomExceptionPortableMethod(true, false));
+            Assert.IsTrue(ex.ToString().Contains(
+                "Call completed with error, but error serialization failed [errType=CustomExceptionPortable, " +
+                "serializationErrMsg=Expected exception in CustomExceptionPortable.WritePortable]"));
+
+            ex = Assert.Throws<ServiceInvocationException>(() => prx.CustomExceptionPortableMethod(true, true));
+            Assert.IsTrue(ex.ToString().Contains(
+                "Call completed with error, but error serialization failed [errType=CustomExceptionPortable, " +
+                "serializationErrMsg=Expected exception in CustomExceptionPortable.WritePortable]"));
+        }
+
+        /// <summary>
+        /// Creates the proxy.
+        /// </summary>
+        protected ITestIgniteServiceProxyInterface GetProxy()
+        {
+            return GetProxy<ITestIgniteServiceProxyInterface>();
+        }
+
+        /// <summary>
+        /// Creates the proxy.
+        /// </summary>
+        protected T GetProxy<T>()
+        {
+            _svc = new TestIgniteService(Portables);
+
+            var prx = new ServiceProxy<T>(InvokeProxyMethod).GetTransparentProxy();
+
+            Assert.IsFalse(ReferenceEquals(_svc, prx));
+
+            return prx;
+        }
+
+        /// <summary>
+        /// Invokes the proxy.
+        /// </summary>
+        /// <param name="method">Method.</param>
+        /// <param name="args">Arguments.</param>
+        /// <returns>
+        /// Invocation result.
+        /// </returns>
+        private object InvokeProxyMethod(MethodBase method, object[] args)
+        {
+            using (var inStream = new PlatformMemoryStream(_memory.Allocate()))
+            using (var outStream = new PlatformMemoryStream(_memory.Allocate()))
+            {
+                // 1) Write to a stream
+                inStream.WriteBool(SrvKeepPortable);  // WriteProxyMethod does not do this, but Java does
+
+                ServiceProxySerializer.WriteProxyMethod(_marsh.StartMarshal(inStream), method, args);
+
+                inStream.SynchronizeOutput();
+
+                inStream.Seek(0, SeekOrigin.Begin);
+
+                // 2) call InvokeServiceMethod
+                string mthdName;
+                object[] mthdArgs;
+
+                ServiceProxySerializer.ReadProxyMethod(inStream, _marsh, out mthdName, out mthdArgs);
+
+                var result = ServiceProxyInvoker.InvokeServiceMethod(_svc, mthdName, mthdArgs);
+
+                ServiceProxySerializer.WriteInvocationResult(outStream, _marsh, result.Key, result.Value);
+                
+                _marsh.StartMarshal(outStream).WriteString("unused");  // fake Java exception details
+
+                outStream.SynchronizeOutput();
+
+                outStream.Seek(0, SeekOrigin.Begin);
+
+                return ServiceProxySerializer.ReadInvocationResult(outStream, _marsh, KeepPortable);
+            }
+        }
+
+        /// <summary>
+        /// Test service interface.
+        /// </summary>
+        protected interface ITestIgniteServiceProperties
+        {
+            /** */
+            int IntProp { get; set; }
+
+            /** */
+            object ObjProp { get; set; }
+
+            /** */
+            string InvokeResult { get; }
+        }
+
+        /// <summary>
+        /// Test service interface to check ambiguity handling.
+        /// </summary>
+        protected interface ITestIgniteServiceAmbiguity
+        {
+            /** */
+            int AmbiguousMethod(int arg);
+        }
+
+        /// <summary>
+        /// Test service interface.
+        /// </summary>
+        protected interface ITestIgniteService : ITestIgniteServiceProperties
+        {
+            /** */
+            void VoidMethod();
+
+            /** */
+            void VoidMethod(int arg);
+
+            /** */
+            void VoidMethod(int arg, string arg1, object arg2 = null);
+
+            /** */
+            void VoidMethod(int arg, string arg1, object arg2 = null, params object[] args);
+
+            /** */
+            object ObjectMethod();
+
+            /** */
+            object ObjectMethod(int arg);
+
+            /** */
+            object ObjectMethod(int arg, string arg1, object arg2 = null);
+
+            /** */
+            object ObjectMethod(int arg, string arg1, object arg2 = null, params object[] args);
+
+            /** */
+            void ExceptionMethod();
+
+            /** */
+            void CustomExceptionMethod();
+
+            /** */
+            void CustomExceptionPortableMethod(bool throwOnWrite, bool throwOnRead);
+
+            /** */
+            TestPortableClass PortableArgMethod(int arg1, IPortableObject arg2);
+
+            /** */
+            IPortableObject PortableResultMethod(int arg1, TestPortableClass arg2);
+
+            /** */
+            IPortableObject PortableArgAndResultMethod(int arg1, IPortableObject arg2);
+
+            /** */
+            int AmbiguousMethod(int arg);
+        }
+
+        /// <summary>
+        /// Test service interface. Does not derive from actual interface, but has all the same method signatures.
+        /// </summary>
+        protected interface ITestIgniteServiceProxyInterface
+        {
+            /** */
+            int IntProp { get; set; }
+
+            /** */
+            object ObjProp { get; set; }
+
+            /** */
+            string InvokeResult { get; }
+
+            /** */
+            void VoidMethod();
+
+            /** */
+            void VoidMethod(int arg);
+
+            /** */
+            void VoidMethod(int arg, string arg1, object arg2 = null);
+
+            /** */
+            void VoidMethod(int arg, string arg1, object arg2 = null, params object[] args);
+
+            /** */
+            object ObjectMethod();
+
+            /** */
+            object ObjectMethod(int arg);
+
+            /** */
+            object ObjectMethod(int arg, string arg1, object arg2 = null);
+
+            /** */
+            object ObjectMethod(int arg, string arg1, object arg2 = null, params object[] args);
+
+            /** */
+            void ExceptionMethod();
+
+            /** */
+            void CustomExceptionMethod();
+
+            /** */
+            void CustomExceptionPortableMethod(bool throwOnWrite, bool throwOnRead);
+
+            /** */
+            TestPortableClass PortableArgMethod(int arg1, IPortableObject arg2);
+
+            /** */
+            IPortableObject PortableResultMethod(int arg1, TestPortableClass arg2);
+
+            /** */
+            IPortableObject PortableArgAndResultMethod(int arg1, IPortableObject arg2);
+
+            /** */
+            void MissingMethod();
+
+            /** */
+            int AmbiguousMethod(int arg);
+        }
+
+        /// <summary>
+        /// Test service.
+        /// </summary>
+        [Serializable]
+        private class TestIgniteService : ITestIgniteService, ITestIgniteServiceAmbiguity
+        {
+            /** */
+            private readonly IPortables _portables;
+
+            /// <summary>
+            /// Initializes a new instance of the <see cref="TestIgniteService"/> class.
+            /// </summary>
+            /// <param name="portables">The portables.</param>
+            public TestIgniteService(IPortables portables)
+            {
+                _portables = portables;
+            }
+
+            /** <inheritdoc /> */
+            public int IntProp { get; set; }
+
+            /** <inheritdoc /> */
+            public object ObjProp { get; set; }
+
+            /** <inheritdoc /> */
+            public string InvokeResult { get; private set; }
+
+            /** <inheritdoc /> */
+            public void VoidMethod()
+            {
+                InvokeResult = "VoidMethod";
+            }
+
+            /** <inheritdoc /> */
+            public void VoidMethod(int arg)
+            {
+                InvokeResult = "VoidMethod" + arg;
+            }
+
+            /** <inheritdoc /> */
+            public void VoidMethod(int arg, string arg1, object arg2 = null)
+            {
+                InvokeResult = "VoidMethod" + arg + arg1 + arg2;
+            }
+
+            /** <inheritdoc /> */
+            public void VoidMethod(int arg, string arg1, object arg2 = null, params object[] args)
+            {
+                InvokeResult = "VoidMethod" + arg + arg1 + arg2 + string.Concat(args.Select(x => x.ToString()));
+            }
+
+            /** <inheritdoc /> */
+            public object ObjectMethod()
+            {
+                return "ObjectMethod";
+            }
+
+            /** <inheritdoc /> */
+            public object ObjectMethod(int arg)
+            {
+                return "ObjectMethod" + arg;
+            }
+
+            /** <inheritdoc /> */
+            public object ObjectMethod(int arg, string arg1, object arg2 = null)
+            {
+                return "ObjectMethod" + arg + arg1 + arg2;
+            }
+
+            /** <inheritdoc /> */
+            public object ObjectMethod(int arg, string arg1, object arg2 = null, params object[] args)
+            {
+                return "ObjectMethod" + arg + arg1 + arg2 + string.Concat(args.Select(x => x.ToString()));
+            }
+
+            /** <inheritdoc /> */
+            public void ExceptionMethod()
+            {
+                throw new ArithmeticException("Expected exception");
+            }
+
+            /** <inheritdoc /> */
+            public void CustomExceptionMethod()
+            {
+                throw new CustomException();
+            }
+
+            /** <inheritdoc /> */
+            public void CustomExceptionPortableMethod(bool throwOnWrite, bool throwOnRead)
+            {
+                throw new CustomExceptionPortable {ThrowOnRead = throwOnRead, ThrowOnWrite = throwOnWrite};
+            }
+
+            /** <inheritdoc /> */
+            public TestPortableClass PortableArgMethod(int arg1, IPortableObject arg2)
+            {
+                return arg2.Deserialize<TestPortableClass>();
+            }
+
+            /** <inheritdoc /> */
+            public IPortableObject PortableResultMethod(int arg1, TestPortableClass arg2)
+            {
+                return _portables.ToPortable<IPortableObject>(arg2);
+            }
+
+            /** <inheritdoc /> */
+            public IPortableObject PortableArgAndResultMethod(int arg1, IPortableObject arg2)
+            {
+                return _portables.ToPortable<IPortableObject>(arg2.Deserialize<TestPortableClass>());
+            }
+
+            /** <inheritdoc /> */
+            public override string ToString()
+            {
+                return IntProp.ToString();
+            }
+
+            /** <inheritdoc /> */
+            public override int GetHashCode()
+            {
+                return IntProp.GetHashCode();
+            }
+
+            /** <inheritdoc /> */
+            int ITestIgniteService.AmbiguousMethod(int arg)
+            {
+                return arg;
+            }
+
+            /** <inheritdoc /> */
+            int ITestIgniteServiceAmbiguity.AmbiguousMethod(int arg)
+            {
+                return -arg;
+            }
+        }
+
+        /// <summary>
+        /// Test serializable class.
+        /// </summary>
+        [Serializable]
+        private class TestClass
+        {
+            /** */
+            public string Prop { get; set; }
+
+            /** <inheritdoc /> */
+            public override string ToString()
+            {
+                return "TestClass" + Prop;
+            }
+        }
+
+        /// <summary>
+        /// Custom non-serializable exception.
+        /// </summary>
+        private class CustomException : Exception
+        {
+            
+        }
+
+        /// <summary>
+        /// Custom non-serializable exception.
+        /// </summary>
+        private class CustomExceptionPortable : Exception, IPortableMarshalAware
+        {
+            /** */
+            public bool ThrowOnWrite { get; set; }
+
+            /** */
+            public bool ThrowOnRead { get; set; }
+
+            /** <inheritdoc /> */
+            public void WritePortable(IPortableWriter writer)
+            {
+                writer.WriteBoolean("ThrowOnRead", ThrowOnRead);
+
+                if (ThrowOnWrite)
+                    throw new Exception("Expected exception in CustomExceptionPortable.WritePortable");
+            }
+
+            /** <inheritdoc /> */
+            public void ReadPortable(IPortableReader reader)
+            {
+                ThrowOnRead = reader.ReadBoolean("ThrowOnRead");
+
+                if (ThrowOnRead)
+                    throw new Exception("Expected exception in CustomExceptionPortable.ReadPortable");
+            }
+        }
+
+        /// <summary>
+        /// Portable object for method argument/result.
+        /// </summary>
+        protected class TestPortableClass : IPortableMarshalAware
+        {
+            /** */
+            public string Prop { get; set; }
+
+            /** */
+            public bool ThrowOnWrite { get; set; }
+
+            /** */
+            public bool ThrowOnRead { get; set; }
+
+            /** <inheritdoc /> */
+            public void WritePortable(IPortableWriter writer)
+            {
+                writer.WriteString("Prop", Prop);
+                writer.WriteBoolean("ThrowOnRead", ThrowOnRead);
+
+                if (ThrowOnWrite)
+                    throw new Exception("Expected exception in TestPortableClass.WritePortable");
+            }
+
+            /** <inheritdoc /> */
+            public void ReadPortable(IPortableReader reader)
+            {
+                Prop = reader.ReadString("Prop");
+                ThrowOnRead = reader.ReadBoolean("ThrowOnRead");
+
+                if (ThrowOnRead)
+                    throw new Exception("Expected exception in TestPortableClass.ReadPortable");
+            }
+        }
+    }
+
+    /// <summary>
+    /// Tests <see cref="ServiceProxySerializer"/> functionality with keepPortable mode enabled on client.
+    /// </summary>
+    public class ServiceProxyTestKeepPortableClient : ServiceProxyTest
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ServiceProxyTestKeepPortableClient"/> class.
+        /// </summary>
+        public ServiceProxyTestKeepPortableClient()
+        {
+            KeepPortable = true;
+        }
+
+        [Test]
+        public void TestPortableMethods()
+        {
+            var prx = GetProxy();
+
+            var obj = new TestPortableClass { Prop = "PropValue" };
+
+            var result = prx.PortableResultMethod(1, obj);
+
+            Assert.AreEqual(obj.Prop, result.Deserialize<TestPortableClass>().Prop);
+        }
+    }
+
+    /// <summary>
+    /// Tests <see cref="ServiceProxySerializer"/> functionality with keepPortable mode enabled on server.
+    /// </summary>
+    public class ServiceProxyTestKeepPortableServer : ServiceProxyTest
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ServiceProxyTestKeepPortableServer"/> class.
+        /// </summary>
+        public ServiceProxyTestKeepPortableServer()
+        {
+            SrvKeepPortable = true;
+        }
+
+        [Test]
+        public void TestPortableMethods()
+        {
+            var prx = GetProxy();
+
+            var obj = new TestPortableClass { Prop = "PropValue" };
+            var portObj = Portables.ToPortable<IPortableObject>(obj);
+
+            var result = prx.PortableArgMethod(1, portObj);
+
+            Assert.AreEqual(obj.Prop, result.Prop);
+        }
+    }
+
+    /// <summary>
+    /// Tests <see cref="ServiceProxySerializer"/> functionality with keepPortable mode enabled on client and on server.
+    /// </summary>
+    public class ServiceProxyTestKeepPortableClientServer : ServiceProxyTest
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ServiceProxyTestKeepPortableClientServer"/> class.
+        /// </summary>
+        public ServiceProxyTestKeepPortableClientServer()
+        {
+            KeepPortable = true;
+            SrvKeepPortable = true;
+        }
+
+        [Test]
+        public void TestPortableMethods()
+        {
+            var prx = GetProxy();
+            
+            var obj = new TestPortableClass { Prop = "PropValue" };
+            var portObj = Portables.ToPortable<IPortableObject>(obj);
+
+            var result = prx.PortableArgAndResultMethod(1, portObj);
+
+            Assert.AreEqual(obj.Prop, result.Deserialize<TestPortableClass>().Prop);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/65bb69da/modules/platform/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs
----------------------------------------------------------------------
diff --git a/modules/platform/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs
new file mode 100644
index 0000000..ba45dbd
--- /dev/null
+++ b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs
@@ -0,0 +1,174 @@
+/*
+ * 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.Tests.Services
+{
+    using System.Collections.Generic;
+    using System.Diagnostics;
+    using Apache.Ignite.Core.Cluster;
+    using Apache.Ignite.Core.Common;
+    using Apache.Ignite.Core.Services;
+
+    /// <summary>
+    /// Services async wrapper to simplify testing.
+    /// </summary>
+    public class ServicesAsyncWrapper : IServices
+    {
+        /** Wrapped async services. */
+        private readonly IServices _services;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ServicesAsyncWrapper"/> class.
+        /// </summary>
+        /// <param name="services">Services to wrap.</param>
+        public ServicesAsyncWrapper(IServices services)
+        {
+            _services = services.WithAsync();
+        }
+
+        /** <inheritDoc /> */
+        public IServices WithAsync()
+        {
+            return this;
+        }
+
+        /** <inheritDoc /> */
+        public bool IsAsync
+        {
+            get { return true; }
+        }
+
+        /** <inheritDoc /> */
+        public IFuture GetFuture()
+        {
+            Debug.Fail("ServicesAsyncWrapper.Future() should not be called. It always returns null.");
+            return null;
+        }
+
+        /** <inheritDoc /> */
+        public IFuture<TResult> GetFuture<TResult>()
+        {
+            Debug.Fail("ServicesAsyncWrapper.Future() should not be called. It always returns null.");
+            return null;
+        }
+
+        /** <inheritDoc /> */
+        public IClusterGroup ClusterGroup
+        {
+            get { return _services.ClusterGroup; }
+        }
+
+        /** <inheritDoc /> */
+        public void DeployClusterSingleton(string name, IService service)
+        {
+            _services.DeployClusterSingleton(name, service);
+            WaitResult();
+        }
+
+        /** <inheritDoc /> */
+        public void DeployNodeSingleton(string name, IService service)
+        {
+            _services.DeployNodeSingleton(name, service);
+            WaitResult();
+        }
+
+        /** <inheritDoc /> */
+        public void DeployKeyAffinitySingleton<TK>(string name, IService service, string cacheName, TK affinityKey)
+        {
+            _services.DeployKeyAffinitySingleton(name, service, cacheName, affinityKey);
+            WaitResult();
+        }
+
+        /** <inheritDoc /> */
+        public void DeployMultiple(string name, IService service, int totalCount, int maxPerNodeCount)
+        {
+            _services.DeployMultiple(name, service, totalCount, maxPerNodeCount);
+            WaitResult();
+        }
+
+        /** <inheritDoc /> */
+        public void Deploy(ServiceConfiguration configuration)
+        {
+            _services.Deploy(configuration);
+            WaitResult();
+        }
+
+        /** <inheritDoc /> */
+        public void Cancel(string name)
+        {
+            _services.Cancel(name);
+            WaitResult();
+        }
+
+        /** <inheritDoc /> */
+        public void CancelAll()
+        {
+            _services.CancelAll();
+            WaitResult();
+        }
+
+        /** <inheritDoc /> */
+        public ICollection<IServiceDescriptor> GetServiceDescriptors()
+        {
+            return _services.GetServiceDescriptors();
+        }
+
+        /** <inheritDoc /> */
+        public T GetService<T>(string name)
+        {
+            return _services.GetService<T>(name);
+        }
+
+        /** <inheritDoc /> */
+        public ICollection<T> GetServices<T>(string name)
+        {
+            return _services.GetServices<T>(name);
+        }
+
+        /** <inheritDoc /> */
+        public T GetServiceProxy<T>(string name) where T : class
+        {
+            return _services.GetServiceProxy<T>(name);
+        }
+
+        /** <inheritDoc /> */
+        public T GetServiceProxy<T>(string name, bool sticky) where T : class
+        {
+            return _services.GetServiceProxy<T>(name, sticky);
+        }
+
+        /** <inheritDoc /> */
+        public IServices WithKeepPortable()
+        {
+            return new ServicesAsyncWrapper(_services.WithKeepPortable());
+        }
+
+        /** <inheritDoc /> */
+        public IServices WithServerKeepPortable()
+        {
+            return new ServicesAsyncWrapper(_services.WithServerKeepPortable());
+        }
+
+        /// <summary>
+        /// Waits for the async result.
+        /// </summary>
+        private void WaitResult()
+        {
+            _services.GetFuture().Get();
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/65bb69da/modules/platform/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
----------------------------------------------------------------------
diff --git a/modules/platform/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
new file mode 100644
index 0000000..6b2a7ec
--- /dev/null
+++ b/modules/platform/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
@@ -0,0 +1,823 @@
+/*
+ * 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.Tests.Services
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Threading;
+    using Apache.Ignite.Core.Cluster;
+    using Apache.Ignite.Core.Common;
+    using Apache.Ignite.Core.Portable;
+    using Apache.Ignite.Core.Resource;
+    using Apache.Ignite.Core.Services;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Services tests.
+    /// </summary>
+    public class ServicesTest
+    {
+        /** */
+        private const string SvcName = "Service1";
+
+        /** */
+        private const string CacheName = "cache1";
+
+        /** */
+        private const int AffKey = 25;
+
+        /** */
+        protected IIgnite Grid1;
+
+        /** */
+        protected IIgnite Grid2;
+
+        /** */
+        protected IIgnite Grid3;
+
+        /** */
+        protected IIgnite[] Grids;
+
+        [TestFixtureTearDown]
+        public void FixtureTearDown()
+        {
+            StopGrids();
+        }
+
+        /// <summary>
+        /// Executes before each test.
+        /// </summary>
+        [SetUp]
+        public void SetUp()
+        {
+            StartGrids();
+            EventsTestHelper.ListenResult = true;
+        }
+
+        /// <summary>
+        /// Executes after each test.
+        /// </summary>
+        [TearDown]
+        public void TearDown()
+        {
+            try
+            {
+                Services.Cancel(SvcName);
+
+                TestUtils.AssertHandleRegistryIsEmpty(1000, Grid1, Grid2, Grid3);
+            }
+            catch (Exception)
+            {
+                // Restart grids to cleanup
+                StopGrids();
+
+                throw;
+            }
+            finally
+            {
+                EventsTestHelper.AssertFailures();
+
+                if (TestContext.CurrentContext.Test.Name.StartsWith("TestEventTypes"))
+                    StopGrids(); // clean events for other tests
+            }
+        }
+
+        /// <summary>
+        /// Tests deployment.
+        /// </summary>
+        [Test]
+        public void TestDeploy([Values(true, false)] bool portable)
+        {
+            var cfg = new ServiceConfiguration
+            {
+                Name = SvcName,
+                MaxPerNodeCount = 3,
+                TotalCount = 3,
+                NodeFilter = new NodeFilter {NodeId = Grid1.GetCluster().GetLocalNode().Id},
+                Service = portable ? new TestIgniteServicePortable() : new TestIgniteServiceSerializable()
+            };
+
+            Services.Deploy(cfg);
+
+            CheckServiceStarted(Grid1, 3);
+        }
+
+        /// <summary>
+        /// Tests cluster singleton deployment.
+        /// </summary>
+        [Test]
+        public void TestDeployClusterSingleton()
+        {
+            var svc = new TestIgniteServiceSerializable();
+
+            Services.DeployClusterSingleton(SvcName, svc);
+
+            var svc0 = Services.GetServiceProxy<ITestIgniteService>(SvcName);
+
+            // Check that only one node has the service.
+            foreach (var grid in Grids)
+            {
+                if (grid.GetCluster().GetLocalNode().Id == svc0.NodeId)
+                    CheckServiceStarted(grid);
+                else
+                    Assert.IsNull(grid.GetServices().GetService<TestIgniteServiceSerializable>(SvcName));
+            }
+        }
+
+        /// <summary>
+        /// Tests node singleton deployment.
+        /// </summary>
+        [Test]
+        public void TestDeployNodeSingleton()
+        {
+            var svc = new TestIgniteServiceSerializable();
+
+            Services.DeployNodeSingleton(SvcName, svc);
+
+            Assert.AreEqual(1, Grid1.GetServices().GetServices<ITestIgniteService>(SvcName).Count);
+            Assert.AreEqual(1, Grid2.GetServices().GetServices<ITestIgniteService>(SvcName).Count);
+            Assert.AreEqual(1, Grid3.GetServices().GetServices<ITestIgniteService>(SvcName).Count);
+        }
+
+        /// <summary>
+        /// Tests key affinity singleton deployment.
+        /// </summary>
+        [Test]
+        public void TestDeployKeyAffinitySingleton()
+        {
+            var svc = new TestIgniteServicePortable();
+
+            Services.DeployKeyAffinitySingleton(SvcName, svc, CacheName, AffKey);
+
+            var affNode = Grid1.GetAffinity(CacheName).MapKeyToNode(AffKey);
+
+            var prx = Services.GetServiceProxy<ITestIgniteService>(SvcName);
+
+            Assert.AreEqual(affNode.Id, prx.NodeId);
+        }
+
+        /// <summary>
+        /// Tests key affinity singleton deployment.
+        /// </summary>
+        [Test]
+        public void TestDeployKeyAffinitySingletonPortable()
+        {
+            var services = Services.WithKeepPortable();
+
+            var svc = new TestIgniteServicePortable();
+
+            var affKey = new PortableObject {Val = AffKey};
+
+            services.DeployKeyAffinitySingleton(SvcName, svc, CacheName, affKey);
+
+            var prx = services.GetServiceProxy<ITestIgniteService>(SvcName);
+
+            Assert.IsTrue(prx.Initialized);
+        }
+
+        /// <summary>
+        /// Tests multiple deployment.
+        /// </summary>
+        [Test]
+        public void TestDeployMultiple()
+        {
+            var svc = new TestIgniteServiceSerializable();
+
+            Services.DeployMultiple(SvcName, svc, Grids.Length * 5, 5);
+
+            foreach (var grid in Grids)
+                CheckServiceStarted(grid, 5);
+        }
+
+        /// <summary>
+        /// Tests cancellation.
+        /// </summary>
+        [Test]
+        public void TestCancel()
+        {
+            for (var i = 0; i < 10; i++)
+            {
+                Services.DeployNodeSingleton(SvcName + i, new TestIgniteServicePortable());
+                Assert.IsNotNull(Services.GetService<ITestIgniteService>(SvcName + i));
+            }
+
+            Services.Cancel(SvcName + 0);
+            Services.Cancel(SvcName + 1);
+
+            Assert.IsNull(Services.GetService<ITestIgniteService>(SvcName + 0));
+            Assert.IsNull(Services.GetService<ITestIgniteService>(SvcName + 1));
+
+            for (var i = 2; i < 10; i++)
+                Assert.IsNotNull(Services.GetService<ITestIgniteService>(SvcName + i));
+
+            Services.CancelAll();
+
+            for (var i = 0; i < 10; i++)
+                Assert.IsNull(Services.GetService<ITestIgniteService>(SvcName + i));
+        }
+
+        /// <summary>
+        /// Tests service proxy.
+        /// </summary>
+        [Test]
+        public void TestGetServiceProxy([Values(true, false)] bool portable)
+        {
+            // Test proxy without a service
+            var prx = Services.GetServiceProxy<ITestIgniteService>(SvcName);
+
+            Assert.IsTrue(prx != null);
+
+            var ex = Assert.Throws<ServiceInvocationException>(() => Assert.IsTrue(prx.Initialized)).InnerException;
+            Assert.AreEqual("Failed to find deployed service: " + SvcName, ex.Message);
+
+            // Deploy to grid2 & grid3
+            var svc = portable
+                ? new TestIgniteServicePortable {TestProperty = 17}
+                : new TestIgniteServiceSerializable {TestProperty = 17};
+
+            Grid1.GetCluster().ForNodeIds(Grid2.GetCluster().GetLocalNode().Id, Grid3.GetCluster().GetLocalNode().Id).GetServices()
+                .DeployNodeSingleton(SvcName,
+                    svc);
+
+            // Make sure there is no local instance on grid1
+            Assert.IsNull(Services.GetService<ITestIgniteService>(SvcName));
+
+            // Get proxy
+            prx = Services.GetServiceProxy<ITestIgniteService>(SvcName);
+
+            // Check proxy properties
+            Assert.IsNotNull(prx);
+            Assert.AreEqual(prx.GetType(), svc.GetType());
+            Assert.AreEqual(prx.ToString(), svc.ToString());
+            Assert.AreEqual(17, prx.TestProperty);
+            Assert.IsTrue(prx.Initialized);
+            Assert.IsTrue(prx.Executed);
+            Assert.IsFalse(prx.Cancelled);
+            Assert.AreEqual(SvcName, prx.LastCallContextName);
+
+            // Check err method
+            Assert.Throws<ServiceInvocationException>(() => prx.ErrMethod(123));
+
+            // Check local scenario (proxy should not be created for local instance)
+            Assert.IsTrue(ReferenceEquals(Grid2.GetServices().GetService<ITestIgniteService>(SvcName),
+                Grid2.GetServices().GetServiceProxy<ITestIgniteService>(SvcName)));
+
+            // Check sticky = false: call multiple times, check that different nodes get invoked
+            var invokedIds = Enumerable.Range(1, 100).Select(x => prx.NodeId).Distinct().ToList();
+            Assert.AreEqual(2, invokedIds.Count);
+
+            // Check sticky = true: all calls should be to the same node
+            prx = Services.GetServiceProxy<ITestIgniteService>(SvcName, true);
+            invokedIds = Enumerable.Range(1, 100).Select(x => prx.NodeId).Distinct().ToList();
+            Assert.AreEqual(1, invokedIds.Count);
+
+            // Proxy does not work for cancelled service.
+            Services.CancelAll();
+
+            Assert.Throws<ServiceInvocationException>(() => { Assert.IsTrue(prx.Cancelled); });
+        }
+
+        /// <summary>
+        /// Tests the duck typing: proxy interface can be different from actual service interface, 
+        /// only called method signature should be compatible.
+        /// </summary>
+        [Test]
+        public void TestDuckTyping([Values(true, false)] bool local)
+        {
+            var svc = new TestIgniteServicePortable {TestProperty = 33};
+
+            // Deploy locally or to the remote node
+            var nodeId = (local ? Grid1 : Grid2).GetCluster().GetLocalNode().Id;
+            
+            var cluster = Grid1.GetCluster().ForNodeIds(nodeId);
+
+            cluster.GetServices().DeployNodeSingleton(SvcName, svc);
+
+            // Get proxy
+            var prx = Services.GetServiceProxy<ITestIgniteServiceProxyInterface>(SvcName);
+
+            // NodeId signature is the same as in service
+            Assert.AreEqual(nodeId, prx.NodeId);
+            
+            // Method signature is different from service signature (object -> object), but is compatible.
+            Assert.AreEqual(15, prx.Method(15));
+
+            // TestProperty is object in proxy and int in service, getter works..
+            Assert.AreEqual(33, prx.TestProperty);
+
+            // .. but setter does not
+            var ex = Assert.Throws<ServiceInvocationException>(() => { prx.TestProperty = new object(); });
+            Assert.AreEqual("Object of type 'System.Object' cannot be converted to type 'System.Int32'.",
+                ex.InnerException.Message);
+        }
+
+        /// <summary>
+        /// Tests service descriptors.
+        /// </summary>
+        [Test]
+        public void TestServiceDescriptors()
+        {
+            Services.DeployKeyAffinitySingleton(SvcName, new TestIgniteServiceSerializable(), CacheName, 1);
+
+            var descriptors = Services.GetServiceDescriptors();
+
+            Assert.AreEqual(1, descriptors.Count);
+
+            var desc = descriptors.Single();
+
+            Assert.AreEqual(SvcName, desc.Name);
+            Assert.AreEqual(CacheName, desc.CacheName);
+            Assert.AreEqual(1, desc.AffinityKey);
+            Assert.AreEqual(1, desc.MaxPerNodeCount);
+            Assert.AreEqual(1, desc.TotalCount);
+            Assert.AreEqual(typeof(TestIgniteServiceSerializable), desc.Type);
+            Assert.AreEqual(Grid1.GetCluster().GetLocalNode().Id, desc.OriginNodeId);
+
+            var top = desc.TopologySnapshot;
+            var prx = Services.GetServiceProxy<ITestIgniteService>(SvcName);
+            
+            Assert.AreEqual(1, top.Count);
+            Assert.AreEqual(prx.NodeId, top.Keys.Single());
+            Assert.AreEqual(1, top.Values.Single());
+        }
+
+        /// <summary>
+        /// Tests the client portable flag.
+        /// </summary>
+        [Test]
+        public void TestWithKeepPortableClient()
+        {
+            var svc = new TestIgniteServicePortable();
+
+            // Deploy to grid2
+            Grid1.GetCluster().ForNodeIds(Grid2.GetCluster().GetLocalNode().Id).GetServices().WithKeepPortable()
+                .DeployNodeSingleton(SvcName, svc);
+
+            // Get proxy
+            var prx = Services.WithKeepPortable().GetServiceProxy<ITestIgniteService>(SvcName);
+
+            var obj = new PortableObject {Val = 11};
+
+            var res = (IPortableObject) prx.Method(obj);
+            Assert.AreEqual(11, res.Deserialize<PortableObject>().Val);
+
+            res = (IPortableObject) prx.Method(Grid1.GetPortables().ToPortable<IPortableObject>(obj));
+            Assert.AreEqual(11, res.Deserialize<PortableObject>().Val);
+        }
+        
+        /// <summary>
+        /// Tests the server portable flag.
+        /// </summary>
+        [Test]
+        public void TestWithKeepPortableServer()
+        {
+            var svc = new TestIgniteServicePortable();
+
+            // Deploy to grid2
+            Grid1.GetCluster().ForNodeIds(Grid2.GetCluster().GetLocalNode().Id).GetServices().WithServerKeepPortable()
+                .DeployNodeSingleton(SvcName, svc);
+
+            // Get proxy
+            var prx = Services.WithServerKeepPortable().GetServiceProxy<ITestIgniteService>(SvcName);
+
+            var obj = new PortableObject { Val = 11 };
+
+            var res = (PortableObject) prx.Method(obj);
+            Assert.AreEqual(11, res.Val);
+
+            res = (PortableObject)prx.Method(Grid1.GetPortables().ToPortable<IPortableObject>(obj));
+            Assert.AreEqual(11, res.Val);
+        }
+
+        /// <summary>
+        /// Tests server and client portable flag.
+        /// </summary>
+        [Test]
+        public void TestWithKeepPortableBoth()
+        {
+            var svc = new TestIgniteServicePortable();
+
+            // Deploy to grid2
+            Grid1.GetCluster().ForNodeIds(Grid2.GetCluster().GetLocalNode().Id).GetServices().WithKeepPortable().WithServerKeepPortable()
+                .DeployNodeSingleton(SvcName, svc);
+
+            // Get proxy
+            var prx = Services.WithKeepPortable().WithServerKeepPortable().GetServiceProxy<ITestIgniteService>(SvcName);
+
+            var obj = new PortableObject { Val = 11 };
+
+            var res = (IPortableObject)prx.Method(obj);
+            Assert.AreEqual(11, res.Deserialize<PortableObject>().Val);
+
+            res = (IPortableObject)prx.Method(Grid1.GetPortables().ToPortable<IPortableObject>(obj));
+            Assert.AreEqual(11, res.Deserialize<PortableObject>().Val);
+        }
+
+        /// <summary>
+        /// Tests exception in Initialize.
+        /// </summary>
+        [Test]
+        public void TestInitException()
+        {
+            var svc = new TestIgniteServiceSerializable { ThrowInit = true };
+
+            var ex = Assert.Throws<IgniteException>(() => Services.DeployMultiple(SvcName, svc, Grids.Length, 1));
+            Assert.AreEqual("Expected exception", ex.Message);
+
+            var svc0 = Services.GetService<TestIgniteServiceSerializable>(SvcName);
+
+            Assert.IsNull(svc0);
+        }
+
+        /// <summary>
+        /// Tests exception in Execute.
+        /// </summary>
+        [Test]
+        public void TestExecuteException()
+        {
+            var svc = new TestIgniteServiceSerializable { ThrowExecute = true };
+
+            Services.DeployMultiple(SvcName, svc, Grids.Length, 1);
+
+            var svc0 = Services.GetService<TestIgniteServiceSerializable>(SvcName);
+
+            // Execution failed, but service exists.
+            Assert.IsNotNull(svc0);
+            Assert.IsFalse(svc0.Executed);
+        }
+
+        /// <summary>
+        /// Tests exception in Cancel.
+        /// </summary>
+        [Test]
+        public void TestCancelException()
+        {
+            var svc = new TestIgniteServiceSerializable { ThrowCancel = true };
+
+            Services.DeployMultiple(SvcName, svc, Grids.Length, 1);
+
+            CheckServiceStarted(Grid1);
+
+            Services.CancelAll();
+
+            // Cancellation failed, but service is removed.
+            foreach (var grid in Grids)
+                Assert.IsNull(grid.GetServices().GetService<ITestIgniteService>(SvcName));
+        }
+
+        [Test]
+        public void TestMarshalExceptionOnRead()
+        {
+            var svc = new TestIgniteServicePortableErr();
+
+            var ex = Assert.Throws<IgniteException>(() => Services.DeployMultiple(SvcName, svc, Grids.Length, 1));
+            Assert.AreEqual("Expected exception", ex.Message);
+
+            var svc0 = Services.GetService<TestIgniteServiceSerializable>(SvcName);
+
+            Assert.IsNull(svc0);
+        }
+
+        [Test]
+        public void TestMarshalExceptionOnWrite()
+        {
+            var svc = new TestIgniteServicePortableErr {ThrowOnWrite = true};
+
+            var ex = Assert.Throws<Exception>(() => Services.DeployMultiple(SvcName, svc, Grids.Length, 1));
+            Assert.AreEqual("Expected exception", ex.Message);
+
+            var svc0 = Services.GetService<TestIgniteServiceSerializable>(SvcName);
+
+            Assert.IsNull(svc0);
+        }
+
+        /// <summary>
+        /// Starts the grids.
+        /// </summary>
+        private void StartGrids()
+        {
+            if (Grid1 != null)
+                return;
+
+            Grid1 = Ignition.Start(Configuration("config\\compute\\compute-grid1.xml"));
+            Grid2 = Ignition.Start(Configuration("config\\compute\\compute-grid2.xml"));
+            Grid3 = Ignition.Start(Configuration("config\\compute\\compute-grid3.xml"));
+
+            Grids = new[] { Grid1, Grid2, Grid3 };
+        }
+
+        /// <summary>
+        /// Stops the grids.
+        /// </summary>
+        private void StopGrids()
+        {
+            Grid1 = Grid2 = Grid3 = null;
+            Grids = null;
+
+            Ignition.StopAll(true);
+        }
+
+        /// <summary>
+        /// Checks that service has started on specified grid.
+        /// </summary>
+        private static void CheckServiceStarted(IIgnite grid, int count = 1)
+        {
+            var services = grid.GetServices().GetServices<TestIgniteServiceSerializable>(SvcName);
+
+            Assert.AreEqual(count, services.Count);
+
+            var svc = services.First();
+
+            Assert.IsNotNull(svc);
+
+            Assert.IsTrue(svc.Initialized);
+
+            Thread.Sleep(100);  // Service runs in a separate thread, wait for it to execute.
+
+            Assert.IsTrue(svc.Executed);
+            Assert.IsFalse(svc.Cancelled);
+
+            Assert.AreEqual(grid.GetCluster().GetLocalNode().Id, svc.NodeId);
+        }
+
+        /// <summary>
+        /// Gets the Ignite configuration.
+        /// </summary>
+        private static IgniteConfiguration Configuration(string springConfigUrl)
+        {
+            return new IgniteConfiguration
+            {
+                SpringConfigUrl = springConfigUrl,
+                JvmClasspath = TestUtils.CreateTestClasspath(),
+                JvmOptions = TestUtils.TestJavaOptions(),
+                PortableConfiguration = new PortableConfiguration
+                {
+                    TypeConfigurations = new List<PortableTypeConfiguration>
+                    {
+                        new PortableTypeConfiguration(typeof(TestIgniteServicePortable)),
+                        new PortableTypeConfiguration(typeof(TestIgniteServicePortableErr)),
+                        new PortableTypeConfiguration(typeof(PortableObject))
+                    }
+                }
+            };
+        }
+
+        /// <summary>
+        /// Gets the services.
+        /// </summary>
+        protected virtual IServices Services
+        {
+            get { return Grid1.GetServices(); }
+        }
+
+        /// <summary>
+        /// Test service interface for proxying.
+        /// </summary>
+        private interface ITestIgniteService
+        {
+            int TestProperty { get; set; }
+
+            /** */
+            bool Initialized { get; }
+
+            /** */
+            bool Cancelled { get; }
+
+            /** */
+            bool Executed { get; }
+
+            /** */
+            Guid NodeId { get; }
+
+            /** */
+            string LastCallContextName { get; }
+
+            /** */
+            object Method(object arg);
+
+            /** */
+            object ErrMethod(object arg);
+        }
+
+        /// <summary>
+        /// Test service interface for proxy usage.
+        /// Has some of the original interface members with different signatures.
+        /// </summary>
+        private interface ITestIgniteServiceProxyInterface
+        {
+            /** */
+            Guid NodeId { get; }
+
+            /** */
+            object TestProperty { get; set; }
+
+            /** */
+            int Method(int arg);
+        }
+
+        #pragma warning disable 649
+
+        /// <summary>
+        /// Test serializable service.
+        /// </summary>
+        [Serializable]
+        private class TestIgniteServiceSerializable : IService, ITestIgniteService
+        {
+            /** */
+            [InstanceResource]
+            private IIgnite _grid;
+
+            /** <inheritdoc /> */
+            public int TestProperty { get; set; }
+
+            /** <inheritdoc /> */
+            public bool Initialized { get; private set; }
+
+            /** <inheritdoc /> */
+            public bool Cancelled { get; private set; }
+
+            /** <inheritdoc /> */
+            public bool Executed { get; private set; }
+
+            /** <inheritdoc /> */
+            public Guid NodeId
+            {
+                get { return _grid.GetCluster().GetLocalNode().Id; }
+            }
+
+            /** <inheritdoc /> */
+            public string LastCallContextName { get; private set; }
+
+            /** */
+            public bool ThrowInit { get; set; }
+
+            /** */
+            public bool ThrowExecute { get; set; }
+
+            /** */
+            public bool ThrowCancel { get; set; }
+
+            /** */
+            public object Method(object arg)
+            {
+                return arg;
+            }
+
+            /** */
+            public object ErrMethod(object arg)
+            {
+                throw new ArgumentNullException("arg", "ExpectedException");
+            }
+
+            /** <inheritdoc /> */
+            public void Init(IServiceContext context)
+            {
+                if (ThrowInit) 
+                    throw new Exception("Expected exception");
+
+                CheckContext(context);
+
+                Assert.IsFalse(context.IsCancelled);
+                Initialized = true;
+            }
+
+            /** <inheritdoc /> */
+            public void Execute(IServiceContext context)
+            {
+                if (ThrowExecute)
+                    throw new Exception("Expected exception");
+
+                CheckContext(context);
+
+                Assert.IsFalse(context.IsCancelled);
+                Assert.IsTrue(Initialized);
+                Assert.IsFalse(Cancelled);
+
+                Executed = true;
+            }
+
+            /** <inheritdoc /> */
+            public void Cancel(IServiceContext context)
+            {
+                if (ThrowCancel)
+                    throw new Exception("Expected exception");
+
+                CheckContext(context);
+
+                Assert.IsTrue(context.IsCancelled);
+
+                Cancelled = true;
+            }
+
+            /// <summary>
+            /// Checks the service context.
+            /// </summary>
+            private void CheckContext(IServiceContext context)
+            {
+                LastCallContextName = context.Name;
+
+                if (context.AffinityKey != null && !(context.AffinityKey is int))
+                {
+                    var portableObject = context.AffinityKey as IPortableObject;
+                    
+                    var key = portableObject != null
+                        ? portableObject.Deserialize<PortableObject>()
+                        : (PortableObject) context.AffinityKey;
+
+                    Assert.AreEqual(AffKey, key.Val);
+                }
+
+                Assert.IsNotNull(_grid);
+
+                Assert.IsTrue(context.Name.StartsWith(SvcName));
+                Assert.AreNotEqual(Guid.Empty, context.ExecutionId);
+            }
+        }
+
+        /// <summary>
+        /// Test portable service.
+        /// </summary>
+        private class TestIgniteServicePortable : TestIgniteServiceSerializable, IPortableMarshalAware
+        {
+            /** <inheritdoc /> */
+            public void WritePortable(IPortableWriter writer)
+            {
+                writer.WriteInt("TestProp", TestProperty);
+            }
+
+            /** <inheritdoc /> */
+            public void ReadPortable(IPortableReader reader)
+            {
+                TestProperty = reader.ReadInt("TestProp");
+            }
+        }
+
+        /// <summary>
+        /// Test portable service with exceptions in marshalling.
+        /// </summary>
+        private class TestIgniteServicePortableErr : TestIgniteServiceSerializable, IPortableMarshalAware
+        {
+            /** */
+            public bool ThrowOnWrite { get; set; }
+
+            /** <inheritdoc /> */
+            public void WritePortable(IPortableWriter writer)
+            {
+                writer.WriteInt("TestProp", TestProperty);
+                
+                if (ThrowOnWrite)
+                    throw new Exception("Expected exception");
+            }
+
+            /** <inheritdoc /> */
+            public void ReadPortable(IPortableReader reader)
+            {
+                TestProperty = reader.ReadInt("TestProp");
+                
+                throw new Exception("Expected exception");
+            }
+        }
+
+        /// <summary>
+        /// Test node filter.
+        /// </summary>
+        [Serializable]
+        private class NodeFilter : IClusterNodeFilter
+        {
+            /// <summary>
+            /// Gets or sets the node identifier.
+            /// </summary>
+            public Guid NodeId { get; set; }
+
+            /** <inheritdoc /> */
+            public bool Invoke(IClusterNode node)
+            {
+                return node.Id == NodeId;
+            }
+        }
+
+        /// <summary>
+        /// Portable object.
+        /// </summary>
+        private class PortableObject
+        {
+            public int Val { get; set; }
+        }
+    }
+}