diff --git a/src/compiler/csharp_generator.cc b/src/compiler/csharp_generator.cc
index 5a8746df8d163c368ab8c61905ebdfe82892734a..69e2738d53d30bd50aecbfdf03c58611e2548683 100644
--- a/src/compiler/csharp_generator.cc
+++ b/src/compiler/csharp_generator.cc
@@ -81,6 +81,10 @@ std::string GetServerInterfaceName(const ServiceDescriptor* service) {
   return "I" + service->name();
 }
 
+std::string GetServerClassName(const ServiceDescriptor* service) {
+  return service->name() + "Base";
+}
+
 std::string GetCSharpMethodType(MethodType method_type) {
   switch (method_type) {
     case METHODTYPE_NO_STREAMING:
@@ -108,10 +112,14 @@ std::string GetMethodFieldName(const MethodDescriptor *method) {
   return "__Method_" + method->name();
 }
 
-std::string GetMethodRequestParamMaybe(const MethodDescriptor *method) {
+std::string GetMethodRequestParamMaybe(const MethodDescriptor *method,
+                                       bool invocation_param=false) {
   if (method->client_streaming()) {
     return "";
   }
+  if (invocation_param) {
+    return "request, ";
+  }
   return GetClassName(method->input_type()) + " request, ";
 }
 
@@ -242,6 +250,8 @@ void GenerateServiceDescriptorProperty(Printer* out, const ServiceDescriptor *se
 
 void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
   out->Print("// client interface\n");
+  out->Print("[System.Obsolete(\"Client side interfaced will be removed "
+             "in the next release. Use client class directly.\")]\n");
   out->Print("public interface $name$\n", "name",
              GetClientInterfaceName(service));
   out->Print("{\n");
@@ -290,6 +300,8 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
 
 void GenerateServerInterface(Printer* out, const ServiceDescriptor *service) {
   out->Print("// server-side interface\n");
+  out->Print("[System.Obsolete(\"Service implementations should inherit"
+      " from the generated abstract base class instead.\")]\n");
   out->Print("public interface $name$\n", "name",
              GetServerInterfaceName(service));
   out->Print("{\n");
@@ -309,21 +321,64 @@ void GenerateServerInterface(Printer* out, const ServiceDescriptor *service) {
   out->Print("\n");
 }
 
+void GenerateServerClass(Printer* out, const ServiceDescriptor *service) {
+  out->Print("// server-side abstract class\n");
+  out->Print("public abstract class $name$\n", "name",
+             GetServerClassName(service));
+  out->Print("{\n");
+  out->Indent();
+  for (int i = 0; i < service->method_count(); i++) {
+    const MethodDescriptor *method = service->method(i);
+    out->Print(
+        "public virtual $returntype$ $methodname$($request$$response_stream_maybe$, "
+        "ServerCallContext context)\n",
+        "methodname", method->name(), "returntype",
+        GetMethodReturnTypeServer(method), "request",
+        GetMethodRequestParamServer(method), "response_stream_maybe",
+        GetMethodResponseStreamMaybe(method));
+    out->Print("{\n");
+    out->Indent();
+    out->Print("throw new RpcException("
+               "new Status(StatusCode.Unimplemented, \"\"));\n");
+    out->Outdent();
+    out->Print("}\n\n");
+  }
+  out->Outdent();
+  out->Print("}\n");
+  out->Print("\n");
+}
+
 void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
   out->Print("// client stub\n");
   out->Print(
-      "public class $name$ : ClientBase, $interface$\n",
-      "name", GetClientClassName(service), "interface",
-      GetClientInterfaceName(service));
+      "public class $name$ : ClientBase<$name$>, $interface$\n",
+      "name", GetClientClassName(service),
+      "interface", GetClientInterfaceName(service));
   out->Print("{\n");
   out->Indent();
 
   // constructors
-  out->Print(
-      "public $name$(Channel channel) : base(channel)\n",
-      "name", GetClientClassName(service));
+  out->Print("public $name$(Channel channel) : base(channel)\n",
+             "name", GetClientClassName(service));
+  out->Print("{\n");
+  out->Print("}\n");
+  out->Print("public $name$(CallInvoker callInvoker) : base(callInvoker)\n",
+             "name", GetClientClassName(service));
   out->Print("{\n");
   out->Print("}\n");
+  out->Print("///<summary>Protected parameterless constructor to allow creation"
+             " of test doubles.</summary>\n");
+  out->Print("protected $name$() : base()\n",
+             "name", GetClientClassName(service));
+  out->Print("{\n");
+  out->Print("}\n");
+  out->Print("///<summary>Protected constructor to allow creation of configured"
+             " clients.</summary>\n");
+  out->Print("protected $name$(ClientBaseConfiguration configuration)"
+             " : base(configuration)\n",
+             "name", GetClientClassName(service));
+  out->Print("{\n");
+  out->Print("}\n\n");
 
   for (int i = 0; i < service->method_count(); i++) {
     const MethodDescriptor *method = service->method(i);
@@ -331,30 +386,26 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
 
     if (method_type == METHODTYPE_NO_STREAMING) {
       // unary calls have an extra synchronous stub method
-      out->Print(
-          "public $response$ $methodname$($request$ request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))\n",
+      out->Print("public virtual $response$ $methodname$($request$ request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))\n",
           "methodname", method->name(), "request",
           GetClassName(method->input_type()), "response",
           GetClassName(method->output_type()));
       out->Print("{\n");
       out->Indent();
-      out->Print("var call = CreateCall($methodfield$, new CallOptions(headers, deadline, cancellationToken));\n",
-                 "methodfield", GetMethodFieldName(method));
-      out->Print("return Calls.BlockingUnaryCall(call, request);\n");
+      out->Print("return $methodname$(request, new CallOptions(headers, deadline, cancellationToken));\n",
+                 "methodname", method->name());
       out->Outdent();
       out->Print("}\n");
 
       // overload taking CallOptions as a param
-      out->Print(
-                "public $response$ $methodname$($request$ request, CallOptions options)\n",
-                "methodname", method->name(), "request",
-                GetClassName(method->input_type()), "response",
-                GetClassName(method->output_type()));
+      out->Print("public virtual $response$ $methodname$($request$ request, CallOptions options)\n",
+          "methodname", method->name(), "request",
+          GetClassName(method->input_type()), "response",
+          GetClassName(method->output_type()));
       out->Print("{\n");
       out->Indent();
-      out->Print("var call = CreateCall($methodfield$, options);\n",
+      out->Print("return CallInvoker.BlockingUnaryCall($methodfield$, null, options, request);\n",
                  "methodfield", GetMethodFieldName(method));
-      out->Print("return Calls.BlockingUnaryCall(call, request);\n");
       out->Outdent();
       out->Print("}\n");
     }
@@ -364,57 +415,44 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
       method_name += "Async";  // prevent name clash with synchronous method.
     }
     out->Print(
-        "public $returntype$ $methodname$($request_maybe$Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))\n",
-        "methodname", method_name, "request_maybe",
-        GetMethodRequestParamMaybe(method), "returntype",
-        GetMethodReturnTypeClient(method));
+            "public virtual $returntype$ $methodname$($request_maybe$Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))\n",
+            "methodname", method_name, "request_maybe",
+            GetMethodRequestParamMaybe(method), "returntype",
+            GetMethodReturnTypeClient(method));
     out->Print("{\n");
     out->Indent();
-    out->Print("var call = CreateCall($methodfield$, new CallOptions(headers, deadline, cancellationToken));\n",
-               "methodfield", GetMethodFieldName(method));
-    switch (GetMethodType(method)) {
-      case METHODTYPE_NO_STREAMING:
-        out->Print("return Calls.AsyncUnaryCall(call, request);\n");
-        break;
-      case METHODTYPE_CLIENT_STREAMING:
-        out->Print("return Calls.AsyncClientStreamingCall(call);\n");
-        break;
-      case METHODTYPE_SERVER_STREAMING:
-        out->Print(
-            "return Calls.AsyncServerStreamingCall(call, request);\n");
-        break;
-      case METHODTYPE_BIDI_STREAMING:
-        out->Print("return Calls.AsyncDuplexStreamingCall(call);\n");
-        break;
-      default:
-        GOOGLE_LOG(FATAL)<< "Can't get here.";
-    }
+
+    out->Print("return $methodname$($request_maybe$new CallOptions(headers, deadline, cancellationToken));\n",
+               "methodname", method_name,
+               "request_maybe", GetMethodRequestParamMaybe(method, true));
     out->Outdent();
     out->Print("}\n");
 
     // overload taking CallOptions as a param
     out->Print(
-        "public $returntype$ $methodname$($request_maybe$CallOptions options)\n",
+        "public virtual $returntype$ $methodname$($request_maybe$CallOptions options)\n",
         "methodname", method_name, "request_maybe",
         GetMethodRequestParamMaybe(method), "returntype",
         GetMethodReturnTypeClient(method));
     out->Print("{\n");
     out->Indent();
-    out->Print("var call = CreateCall($methodfield$, options);\n",
-               "methodfield", GetMethodFieldName(method));
     switch (GetMethodType(method)) {
       case METHODTYPE_NO_STREAMING:
-        out->Print("return Calls.AsyncUnaryCall(call, request);\n");
+        out->Print("return CallInvoker.AsyncUnaryCall($methodfield$, null, options, request);\n",
+                   "methodfield", GetMethodFieldName(method));
         break;
       case METHODTYPE_CLIENT_STREAMING:
-        out->Print("return Calls.AsyncClientStreamingCall(call);\n");
+        out->Print("return CallInvoker.AsyncClientStreamingCall($methodfield$, null, options);\n",
+                   "methodfield", GetMethodFieldName(method));
         break;
       case METHODTYPE_SERVER_STREAMING:
         out->Print(
-            "return Calls.AsyncServerStreamingCall(call, request);\n");
+            "return CallInvoker.AsyncServerStreamingCall($methodfield$, null, options, request);\n",
+            "methodfield", GetMethodFieldName(method));
         break;
       case METHODTYPE_BIDI_STREAMING:
-        out->Print("return Calls.AsyncDuplexStreamingCall(call);\n");
+        out->Print("return CallInvoker.AsyncDuplexStreamingCall($methodfield$, null, options);\n",
+                   "methodfield", GetMethodFieldName(method));
         break;
       default:
         GOOGLE_LOG(FATAL)<< "Can't get here.";
@@ -422,17 +460,30 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
     out->Outdent();
     out->Print("}\n");
   }
+
+  // override NewInstance method
+  out->Print("protected override $name$ NewInstance(ClientBaseConfiguration configuration)\n",
+             "name", GetClientClassName(service));
+  out->Print("{\n");
+  out->Indent();
+  out->Print("return new $name$(configuration);\n",
+             "name", GetClientClassName(service));
+  out->Outdent();
+  out->Print("}\n");
+
   out->Outdent();
   out->Print("}\n");
   out->Print("\n");
 }
 
-void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor *service) {
+void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor *service,
+                               bool use_server_class) {
   out->Print(
       "// creates service definition that can be registered with a server\n");
   out->Print(
       "public static ServerServiceDefinition BindService($interface$ serviceImpl)\n",
-      "interface", GetServerInterfaceName(service));
+      "interface", use_server_class ? GetServerClassName(service) :
+          GetServerInterfaceName(service));
   out->Print("{\n");
   out->Indent();
 
@@ -489,8 +540,10 @@ void GenerateService(Printer* out, const ServiceDescriptor *service) {
   GenerateServiceDescriptorProperty(out, service);
   GenerateClientInterface(out, service);
   GenerateServerInterface(out, service);
+  GenerateServerClass(out, service);
   GenerateClientStub(out, service);
-  GenerateBindServiceMethod(out, service);
+  GenerateBindServiceMethod(out, service, false);
+  GenerateBindServiceMethod(out, service, true);
   GenerateNewStubMethods(out, service);
 
   out->Outdent();
diff --git a/src/csharp/Grpc.Core/CallInvoker.cs b/src/csharp/Grpc.Core/CallInvoker.cs
new file mode 100644
index 0000000000000000000000000000000000000000..39199b1fd5880c2a51d66abc268a6fcfefa02778
--- /dev/null
+++ b/src/csharp/Grpc.Core/CallInvoker.cs
@@ -0,0 +1,84 @@
+#region Copyright notice and license
+
+// Copyright 2015-2016, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System.Threading.Tasks;
+using Grpc.Core.Internal;
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// Abstraction of client-side RPC invocation.
+    /// </summary>
+    /// <seealso cref="Calls"/>
+    public abstract class CallInvoker
+    {
+        /// <summary>
+        /// Invokes a simple remote call in a blocking fashion.
+        /// </summary>
+        public abstract TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+            where TRequest : class
+            where TResponse : class;
+
+        /// <summary>
+        /// Invokes a simple remote call asynchronously.
+        /// </summary>
+        public abstract AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+            where TRequest : class
+            where TResponse : class;
+
+        /// <summary>
+        /// Invokes a server streaming call asynchronously.
+        /// In server streaming scenario, client sends on request and server responds with a stream of responses.
+        /// </summary>
+        public abstract AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+            where TRequest : class
+            where TResponse : class;
+
+        /// <summary>
+        /// Invokes a client streaming call asynchronously.
+        /// In client streaming scenario, client sends a stream of requests and server responds with a single response.
+        /// </summary>
+        public abstract AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
+            where TRequest : class
+            where TResponse : class;
+
+        /// <summary>
+        /// Invokes a duplex streaming call asynchronously.
+        /// In duplex streaming scenario, client sends a stream of requests and server responds with a stream of responses.
+        /// The response stream is completely independent and both side can be sending messages at the same time.
+        /// </summary>
+        public abstract AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
+            where TRequest : class
+            where TResponse : class;
+    }
+}
diff --git a/src/csharp/Grpc.Core/ClientBase.cs b/src/csharp/Grpc.Core/ClientBase.cs
index e5b398062b2c489891d38cc71ffee03eb274e059..5517233e3ccf56feebc124c661748e19ffab4fe1 100644
--- a/src/csharp/Grpc.Core/ClientBase.cs
+++ b/src/csharp/Grpc.Core/ClientBase.cs
@@ -1,6 +1,6 @@
 #region Copyright notice and license
 
-// Copyright 2015, Google Inc.
+// Copyright 2015-2016, Google Inc.
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without
@@ -31,93 +31,156 @@
 
 #endregion
 
-using System;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
 
 namespace Grpc.Core
 {
     /// <summary>
-    /// Interceptor for call headers.
+    /// Generic base class for client-side stubs.
     /// </summary>
-    /// <remarks>Header interceptor is no longer to recommented way to perform authentication.
-    /// For header (initial metadata) based auth such as OAuth2 or JWT access token, use <see cref="MetadataCredentials"/>.
-    /// </remarks>
-    public delegate void HeaderInterceptor(IMethod method, Metadata metadata);
+    public abstract class ClientBase<T> : ClientBase
+        where T : ClientBase<T>
+    {
+        /// <summary>
+        /// Initializes a new instance of <c>ClientBase</c> class that
+        /// throws <c>NotImplementedException</c> upon invocation of any RPC.
+        /// This constructor is only provided to allow creation of test doubles
+        /// for client classes (e.g. mocking requires a parameterless constructor).
+        /// </summary>
+        protected ClientBase() : base()
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <c>ClientBase</c> class.
+        /// </summary>
+        /// <param name="configuration">The configuration.</param>
+        protected ClientBase(ClientBaseConfiguration configuration) : base(configuration)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <c>ClientBase</c> class.
+        /// </summary>
+        /// <param name="channel">The channel to use for remote call invocation.</param>
+        public ClientBase(Channel channel) : base(channel)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <c>ClientBase</c> class.
+        /// </summary>
+        /// <param name="callInvoker">The <c>CallInvoker</c> for remote call invocation.</param>
+        public ClientBase(CallInvoker callInvoker) : base(callInvoker)
+        {
+        }
+
+        /// <summary>
+        /// Creates a new client that sets host field for calls explicitly.
+        /// gRPC supports multiple "hosts" being served by a single server.
+        /// By default (if a client was not created by calling this method),
+        /// host <c>null</c> with the meaning "use default host" is used.
+        /// </summary>
+        public T WithHost(string host)
+        {
+            var newConfiguration = this.Configuration.WithHost(host);
+            return NewInstance(newConfiguration);
+        }
+
+        /// <summary>
+        /// Creates a new instance of client from given <c>ClientBaseConfiguration</c>.
+        /// </summary>
+        protected abstract T NewInstance(ClientBaseConfiguration configuration);
+    }
 
     /// <summary>
     /// Base class for client-side stubs.
     /// </summary>
     public abstract class ClientBase
     {
-        readonly Channel channel;
+        readonly ClientBaseConfiguration configuration;
+        readonly CallInvoker callInvoker;
+
+        /// <summary>
+        /// Initializes a new instance of <c>ClientBase</c> class that
+        /// throws <c>NotImplementedException</c> upon invocation of any RPC.
+        /// This constructor is only provided to allow creation of test doubles
+        /// for client classes (e.g. mocking requires a parameterless constructor).
+        /// </summary>
+        protected ClientBase() : this(new UnimplementedCallInvoker())
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <c>ClientBase</c> class.
+        /// </summary>
+        /// <param name="configuration">The configuration.</param>
+        protected ClientBase(ClientBaseConfiguration configuration)
+        {
+            this.configuration = GrpcPreconditions.CheckNotNull(configuration, "configuration");
+            this.callInvoker = configuration.CreateDecoratedCallInvoker();
+        }
 
         /// <summary>
         /// Initializes a new instance of <c>ClientBase</c> class.
         /// </summary>
         /// <param name="channel">The channel to use for remote call invocation.</param>
-        public ClientBase(Channel channel)
+        public ClientBase(Channel channel) : this(new DefaultCallInvoker(channel))
         {
-            this.channel = channel;
         }
 
         /// <summary>
-        /// Can be used to register a custom header interceptor.
-        /// The interceptor is invoked each time a new call on this client is started.
-        /// It is not recommented to use header interceptor to add auth headers to RPC calls.
+        /// Initializes a new instance of <c>ClientBase</c> class.
         /// </summary>
-        /// <seealso cref="HeaderInterceptor"/>
-        public HeaderInterceptor HeaderInterceptor
+        /// <param name="callInvoker">The <c>CallInvoker</c> for remote call invocation.</param>
+        public ClientBase(CallInvoker callInvoker) : this(new ClientBaseConfiguration(callInvoker, null))
         {
-            get;
-            set;
         }
 
         /// <summary>
-        /// gRPC supports multiple "hosts" being served by a single server. 
-        /// This property can be used to set the target host explicitly.
-        /// By default, this will be set to <c>null</c> with the meaning
-        /// "use default host".
+        /// Gets the call invoker.
         /// </summary>
-        public string Host
+        protected CallInvoker CallInvoker
         {
-            get;
-            set;
+            get { return this.callInvoker; }
         }
 
         /// <summary>
-        /// Channel associated with this client.
+        /// Gets the configuration.
         /// </summary>
-        public Channel Channel
+        internal ClientBaseConfiguration Configuration
         {
-            get
-            {
-                return this.channel;
-            }
+            get { return this.configuration; }
         }
 
         /// <summary>
-        /// Creates a new call to given method.
+        /// Represents configuration of ClientBase. The class itself is visible to
+        /// subclasses, but contents are marked as internal to make the instances opaque.
+        /// The verbose name of this class was chosen to make name clash in generated code 
+        /// less likely.
         /// </summary>
-        /// <param name="method">The method to invoke.</param>
-        /// <param name="options">The call options.</param>
-        /// <typeparam name="TRequest">Request message type.</typeparam>
-        /// <typeparam name="TResponse">Response message type.</typeparam>
-        /// <returns>The call invocation details.</returns>
-        protected CallInvocationDetails<TRequest, TResponse> CreateCall<TRequest, TResponse>(Method<TRequest, TResponse> method, CallOptions options)
-            where TRequest : class
-            where TResponse : class
+        protected internal class ClientBaseConfiguration
         {
-            var interceptor = HeaderInterceptor;
-            if (interceptor != null)
+            readonly CallInvoker undecoratedCallInvoker;
+            readonly string host;
+
+            internal ClientBaseConfiguration(CallInvoker undecoratedCallInvoker, string host)
+            {
+                this.undecoratedCallInvoker = GrpcPreconditions.CheckNotNull(undecoratedCallInvoker);
+                this.host = host;
+            }
+
+            internal CallInvoker CreateDecoratedCallInvoker()
+            {
+                return new InterceptingCallInvoker(undecoratedCallInvoker, hostInterceptor: (h) => host);
+            }
+
+            internal ClientBaseConfiguration WithHost(string host)
             {
-                if (options.Headers == null)
-                {
-                    options = options.WithHeaders(new Metadata());
-                }
-                interceptor(method, options.Headers);
+                GrpcPreconditions.CheckNotNull(host, "host");
+                return new ClientBaseConfiguration(this.undecoratedCallInvoker, host);
             }
-            return new CallInvocationDetails<TRequest, TResponse>(channel, method, Host, options);
         }
     }
 }
diff --git a/src/csharp/Grpc.Core/DefaultCallInvoker.cs b/src/csharp/Grpc.Core/DefaultCallInvoker.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1a99e41153b3bd5c0419bbe141c97006aaa8676e
--- /dev/null
+++ b/src/csharp/Grpc.Core/DefaultCallInvoker.cs
@@ -0,0 +1,112 @@
+#region Copyright notice and license
+
+// Copyright 2015-2016, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System.Threading.Tasks;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// Invokes client RPCs using <see cref="Calls"/>.
+    /// </summary>
+    public class DefaultCallInvoker : CallInvoker
+    {
+        readonly Channel channel;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Grpc.Core.DefaultCallInvoker"/> class.
+        /// </summary>
+        /// <param name="channel">Channel to use.</param>
+        public DefaultCallInvoker(Channel channel)
+        {
+            this.channel = GrpcPreconditions.CheckNotNull(channel);
+        }
+
+        /// <summary>
+        /// Invokes a simple remote call in a blocking fashion.
+        /// </summary>
+        public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+        {
+            var call = CreateCall(method, host, options);
+            return Calls.BlockingUnaryCall(call, request);
+        }
+
+        /// <summary>
+        /// Invokes a simple remote call asynchronously.
+        /// </summary>
+        public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+        {
+            var call = CreateCall(method, host, options);
+            return Calls.AsyncUnaryCall(call, request);
+        }
+
+        /// <summary>
+        /// Invokes a server streaming call asynchronously.
+        /// In server streaming scenario, client sends on request and server responds with a stream of responses.
+        /// </summary>
+        public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+        {
+            var call = CreateCall(method, host, options);
+            return Calls.AsyncServerStreamingCall(call, request);
+        }
+
+        /// <summary>
+        /// Invokes a client streaming call asynchronously.
+        /// In client streaming scenario, client sends a stream of requests and server responds with a single response.
+        /// </summary>
+        public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
+        {
+            var call = CreateCall(method, host, options);
+            return Calls.AsyncClientStreamingCall(call);
+        }
+
+        /// <summary>
+        /// Invokes a duplex streaming call asynchronously.
+        /// In duplex streaming scenario, client sends a stream of requests and server responds with a stream of responses.
+        /// The response stream is completely independent and both side can be sending messages at the same time.
+        /// </summary>
+        public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
+        {
+            var call = CreateCall(method, host, options);
+            return Calls.AsyncDuplexStreamingCall(call);
+        }
+
+        protected virtual CallInvocationDetails<TRequest, TResponse> CreateCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
+                where TRequest : class
+                where TResponse : class
+        {
+            return new CallInvocationDetails<TRequest, TResponse>(channel, method, host, options);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj
index 3189835ccd913e7a533eab5dbecbe7aa65ce8136..251a6889464cb6e81b9e2854bc899de02fb3b393 100644
--- a/src/csharp/Grpc.Core/Grpc.Core.csproj
+++ b/src/csharp/Grpc.Core/Grpc.Core.csproj
@@ -129,6 +129,10 @@
     <Compile Include="Profiling\Profilers.cs" />
     <Compile Include="Internal\DefaultSslRootsOverride.cs" />
     <Compile Include="Utils\GrpcPreconditions.cs" />
+    <Compile Include="CallInvoker.cs" />
+    <Compile Include="DefaultCallInvoker.cs" />
+    <Compile Include="Internal\UnimplementedCallInvoker.cs" />
+    <Compile Include="Internal\InterceptingCallInvoker.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="Grpc.Core.nuspec" />
diff --git a/src/csharp/Grpc.Core/Internal/InterceptingCallInvoker.cs b/src/csharp/Grpc.Core/Internal/InterceptingCallInvoker.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ef48dc712121c048e392a418c3f6955c738cd569
--- /dev/null
+++ b/src/csharp/Grpc.Core/Internal/InterceptingCallInvoker.cs
@@ -0,0 +1,134 @@
+#region Copyright notice and license
+
+// Copyright 2015-2016, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core.Internal
+{
+    /// <summary>
+    /// Decorates an underlying <c>CallInvoker</c> to intercept call invocations.
+    /// </summary>
+    internal class InterceptingCallInvoker : CallInvoker
+    {
+        readonly CallInvoker callInvoker;
+        readonly Func<string, string> hostInterceptor;
+        readonly Func<CallOptions, CallOptions> callOptionsInterceptor;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Grpc.Core.InterceptingCallInvoker"/> class.
+        /// </summary>
+        public InterceptingCallInvoker(CallInvoker callInvoker,
+            Func<string, string> hostInterceptor = null,
+            Func<CallOptions, CallOptions> callOptionsInterceptor = null)
+        {
+            this.callInvoker = GrpcPreconditions.CheckNotNull(callInvoker);
+            this.hostInterceptor = hostInterceptor;
+            this.callOptionsInterceptor = callOptionsInterceptor;
+        }
+
+        /// <summary>
+        /// Intercepts a unary call.
+        /// </summary>
+        public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+        {
+            host = InterceptHost(host);
+            options = InterceptCallOptions(options);
+            return callInvoker.BlockingUnaryCall(method, host, options, request);
+        }
+
+        /// <summary>
+        /// Invokes a simple remote call asynchronously.
+        /// </summary>
+        public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+        {
+            host = InterceptHost(host);
+            options = InterceptCallOptions(options);
+            return callInvoker.AsyncUnaryCall(method, host, options, request);
+        }
+
+        /// <summary>
+        /// Invokes a server streaming call asynchronously.
+        /// In server streaming scenario, client sends on request and server responds with a stream of responses.
+        /// </summary>
+        public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+        {
+            host = InterceptHost(host);
+            options = InterceptCallOptions(options);
+            return callInvoker.AsyncServerStreamingCall(method, host, options, request);
+        }
+
+        /// <summary>
+        /// Invokes a client streaming call asynchronously.
+        /// In client streaming scenario, client sends a stream of requests and server responds with a single response.
+        /// </summary>
+        public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
+        {
+            host = InterceptHost(host);
+            options = InterceptCallOptions(options);
+            return callInvoker.AsyncClientStreamingCall(method, host, options);
+        }
+
+        /// <summary>
+        /// Invokes a duplex streaming call asynchronously.
+        /// In duplex streaming scenario, client sends a stream of requests and server responds with a stream of responses.
+        /// The response stream is completely independent and both side can be sending messages at the same time.
+        /// </summary>
+        public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
+        {
+            host = InterceptHost(host);
+            options = InterceptCallOptions(options);
+            return callInvoker.AsyncDuplexStreamingCall(method, host, options);
+        }
+
+        private string InterceptHost(string host)
+        {
+            if (hostInterceptor == null)
+            {
+                return host;
+            }
+            return hostInterceptor(host);
+        }
+
+        private CallOptions InterceptCallOptions(CallOptions options)
+        {
+            if (callOptionsInterceptor == null)
+            {
+                return options;
+            }
+            return callOptionsInterceptor(options);
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/Internal/UnimplementedCallInvoker.cs b/src/csharp/Grpc.Core/Internal/UnimplementedCallInvoker.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0c7340873bdf956de063d707a142df642505e3f3
--- /dev/null
+++ b/src/csharp/Grpc.Core/Internal/UnimplementedCallInvoker.cs
@@ -0,0 +1,75 @@
+#region Copyright notice and license
+
+// Copyright 2015-2016, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core.Internal
+{
+    /// <summary>
+    /// Call invoker that throws <c>NotImplementedException</c> for all requests.
+    /// </summary>
+    internal class UnimplementedCallInvoker : CallInvoker
+    {
+        public UnimplementedCallInvoker()
+        {
+        }
+
+        public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Examples.MathClient/MathClient.cs b/src/csharp/Grpc.Examples.MathClient/MathClient.cs
index 64e429ed5a32001bf89b10172ad73c40adb79ad0..aadef6833dd33613c5364afba9b5f8f4840fe9c7 100644
--- a/src/csharp/Grpc.Examples.MathClient/MathClient.cs
+++ b/src/csharp/Grpc.Examples.MathClient/MathClient.cs
@@ -1,5 +1,5 @@
 #region Copyright notice and license
-// Copyright 2015, Google Inc.
+// Copyright 2015-2016, Google Inc.
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without
@@ -40,7 +40,7 @@ namespace Math
         public static void Main(string[] args)
         {
             var channel = new Channel("127.0.0.1", 23456, ChannelCredentials.Insecure);
-            Math.IMathClient client = new Math.MathClient(channel);
+            Math.MathClient client = new Math.MathClient(channel);
             MathExamples.DivExample(client);
 
             MathExamples.DivAsyncExample(client).Wait();
diff --git a/src/csharp/Grpc.Examples/MathExamples.cs b/src/csharp/Grpc.Examples/MathExamples.cs
index 8009ccbbfa1cfd16c269115fc2c87108de395a69..6075420974889cd4cf26e08cb8c088a8e6ff5d12 100644
--- a/src/csharp/Grpc.Examples/MathExamples.cs
+++ b/src/csharp/Grpc.Examples/MathExamples.cs
@@ -1,5 +1,5 @@
 #region Copyright notice and license
-// Copyright 2015, Google Inc.
+// Copyright 2015-2016, Google Inc.
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without
@@ -38,19 +38,19 @@ namespace Math
 {
     public static class MathExamples
     {
-        public static void DivExample(Math.IMathClient client)
+        public static void DivExample(Math.MathClient client)
         {
             DivReply result = client.Div(new DivArgs { Dividend = 10, Divisor = 3 });
             Console.WriteLine("Div Result: " + result);
         }
 
-        public static async Task DivAsyncExample(Math.IMathClient client)
+        public static async Task DivAsyncExample(Math.MathClient client)
         {
             DivReply result = await client.DivAsync(new DivArgs { Dividend = 4, Divisor = 5 });
             Console.WriteLine("DivAsync Result: " + result);
         }
 
-        public static async Task FibExample(Math.IMathClient client)
+        public static async Task FibExample(Math.MathClient client)
         {
             using (var call = client.Fib(new FibArgs { Limit = 5 }))
             {
@@ -59,7 +59,7 @@ namespace Math
             }
         }
 
-        public static async Task SumExample(Math.IMathClient client)
+        public static async Task SumExample(Math.MathClient client)
         {
             var numbers = new List<Num>
             {
@@ -75,7 +75,7 @@ namespace Math
             }
         }
 
-        public static async Task DivManyExample(Math.IMathClient client)
+        public static async Task DivManyExample(Math.MathClient client)
         {
             var divArgsList = new List<DivArgs>
             {
@@ -90,7 +90,7 @@ namespace Math
             }
         }
 
-        public static async Task DependendRequestsExample(Math.IMathClient client)
+        public static async Task DependendRequestsExample(Math.MathClient client)
         {
             var numbers = new List<Num>
             {
diff --git a/src/csharp/Grpc.Examples/MathGrpc.cs b/src/csharp/Grpc.Examples/MathGrpc.cs
index a6e878d0f48b6ea3e0b2e8b6540f8ae82174aaab..f3bb0d1cdcba5e355bba8e23eddf335f570d8f83 100644
--- a/src/csharp/Grpc.Examples/MathGrpc.cs
+++ b/src/csharp/Grpc.Examples/MathGrpc.cs
@@ -52,6 +52,7 @@ namespace Math {
     }
 
     // client interface
+    [System.Obsolete("Client side interfaced will be removed in the next release. Use client class directly.")]
     public interface IMathClient
     {
       global::Math.DivReply Div(global::Math.DivArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
@@ -67,6 +68,7 @@ namespace Math {
     }
 
     // server-side interface
+    [System.Obsolete("Service implementations should inherit from the generated abstract base class instead.")]
     public interface IMath
     {
       Task<global::Math.DivReply> Div(global::Math.DivArgs request, ServerCallContext context);
@@ -75,61 +77,92 @@ namespace Math {
       Task<global::Math.Num> Sum(IAsyncStreamReader<global::Math.Num> requestStream, ServerCallContext context);
     }
 
+    // server-side abstract class
+    public abstract class MathBase
+    {
+      public virtual Task<global::Math.DivReply> Div(global::Math.DivArgs request, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task DivMany(IAsyncStreamReader<global::Math.DivArgs> requestStream, IServerStreamWriter<global::Math.DivReply> responseStream, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task Fib(global::Math.FibArgs request, IServerStreamWriter<global::Math.Num> responseStream, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task<global::Math.Num> Sum(IAsyncStreamReader<global::Math.Num> requestStream, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+    }
+
     // client stub
-    public class MathClient : ClientBase, IMathClient
+    public class MathClient : ClientBase<MathClient>, IMathClient
     {
       public MathClient(Channel channel) : base(channel)
       {
       }
-      public global::Math.DivReply Div(global::Math.DivArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public MathClient(CallInvoker callInvoker) : base(callInvoker)
       {
-        var call = CreateCall(__Method_Div, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.BlockingUnaryCall(call, request);
       }
-      public global::Math.DivReply Div(global::Math.DivArgs request, CallOptions options)
+      ///<summary>Protected parameterless constructor to allow creation of test doubles.</summary>
+      protected MathClient() : base()
       {
-        var call = CreateCall(__Method_Div, options);
-        return Calls.BlockingUnaryCall(call, request);
       }
-      public AsyncUnaryCall<global::Math.DivReply> DivAsync(global::Math.DivArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      ///<summary>Protected constructor to allow creation of configured clients.</summary>
+      protected MathClient(ClientBaseConfiguration configuration) : base(configuration)
       {
-        var call = CreateCall(__Method_Div, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncUnaryCall(call, request);
       }
-      public AsyncUnaryCall<global::Math.DivReply> DivAsync(global::Math.DivArgs request, CallOptions options)
+
+      public virtual global::Math.DivReply Div(global::Math.DivArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_Div, options);
-        return Calls.AsyncUnaryCall(call, request);
+        return Div(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncDuplexStreamingCall<global::Math.DivArgs, global::Math.DivReply> DivMany(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual global::Math.DivReply Div(global::Math.DivArgs request, CallOptions options)
       {
-        var call = CreateCall(__Method_DivMany, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncDuplexStreamingCall(call);
+        return CallInvoker.BlockingUnaryCall(__Method_Div, null, options, request);
       }
-      public AsyncDuplexStreamingCall<global::Math.DivArgs, global::Math.DivReply> DivMany(CallOptions options)
+      public virtual AsyncUnaryCall<global::Math.DivReply> DivAsync(global::Math.DivArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_DivMany, options);
-        return Calls.AsyncDuplexStreamingCall(call);
+        return DivAsync(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncServerStreamingCall<global::Math.Num> Fib(global::Math.FibArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncUnaryCall<global::Math.DivReply> DivAsync(global::Math.DivArgs request, CallOptions options)
       {
-        var call = CreateCall(__Method_Fib, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncServerStreamingCall(call, request);
+        return CallInvoker.AsyncUnaryCall(__Method_Div, null, options, request);
       }
-      public AsyncServerStreamingCall<global::Math.Num> Fib(global::Math.FibArgs request, CallOptions options)
+      public virtual AsyncDuplexStreamingCall<global::Math.DivArgs, global::Math.DivReply> DivMany(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_Fib, options);
-        return Calls.AsyncServerStreamingCall(call, request);
+        return DivMany(new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncClientStreamingCall<global::Math.Num, global::Math.Num> Sum(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncDuplexStreamingCall<global::Math.DivArgs, global::Math.DivReply> DivMany(CallOptions options)
       {
-        var call = CreateCall(__Method_Sum, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncClientStreamingCall(call);
+        return CallInvoker.AsyncDuplexStreamingCall(__Method_DivMany, null, options);
       }
-      public AsyncClientStreamingCall<global::Math.Num, global::Math.Num> Sum(CallOptions options)
+      public virtual AsyncServerStreamingCall<global::Math.Num> Fib(global::Math.FibArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_Sum, options);
-        return Calls.AsyncClientStreamingCall(call);
+        return Fib(request, new CallOptions(headers, deadline, cancellationToken));
+      }
+      public virtual AsyncServerStreamingCall<global::Math.Num> Fib(global::Math.FibArgs request, CallOptions options)
+      {
+        return CallInvoker.AsyncServerStreamingCall(__Method_Fib, null, options, request);
+      }
+      public virtual AsyncClientStreamingCall<global::Math.Num, global::Math.Num> Sum(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        return Sum(new CallOptions(headers, deadline, cancellationToken));
+      }
+      public virtual AsyncClientStreamingCall<global::Math.Num, global::Math.Num> Sum(CallOptions options)
+      {
+        return CallInvoker.AsyncClientStreamingCall(__Method_Sum, null, options);
+      }
+      protected override MathClient NewInstance(ClientBaseConfiguration configuration)
+      {
+        return new MathClient(configuration);
       }
     }
 
@@ -143,6 +176,16 @@ namespace Math {
           .AddMethod(__Method_Sum, serviceImpl.Sum).Build();
     }
 
+    // creates service definition that can be registered with a server
+    public static ServerServiceDefinition BindService(MathBase serviceImpl)
+    {
+      return ServerServiceDefinition.CreateBuilder(__ServiceName)
+          .AddMethod(__Method_Div, serviceImpl.Div)
+          .AddMethod(__Method_DivMany, serviceImpl.DivMany)
+          .AddMethod(__Method_Fib, serviceImpl.Fib)
+          .AddMethod(__Method_Sum, serviceImpl.Sum).Build();
+    }
+
     // creates a new client
     public static MathClient NewClient(Channel channel)
     {
diff --git a/src/csharp/Grpc.Examples/MathServiceImpl.cs b/src/csharp/Grpc.Examples/MathServiceImpl.cs
index 71dc655e46d12584a7642d89e872095cb2503665..79c56e57a85a220cb6d833f552134f361e2ab6d9 100644
--- a/src/csharp/Grpc.Examples/MathServiceImpl.cs
+++ b/src/csharp/Grpc.Examples/MathServiceImpl.cs
@@ -1,6 +1,6 @@
 #region Copyright notice and license
 
-// Copyright 2015, Google Inc.
+// Copyright 2015-2016, Google Inc.
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without
@@ -43,14 +43,14 @@ namespace Math
     /// <summary>
     /// Implementation of MathService server
     /// </summary>
-    public class MathServiceImpl : Math.IMath
+    public class MathServiceImpl : Math.MathBase
     {
-        public Task<DivReply> Div(DivArgs request, ServerCallContext context)
+        public override Task<DivReply> Div(DivArgs request, ServerCallContext context)
         {
             return Task.FromResult(DivInternal(request));
         }
 
-        public async Task Fib(FibArgs request, IServerStreamWriter<Num> responseStream, ServerCallContext context)
+        public override async Task Fib(FibArgs request, IServerStreamWriter<Num> responseStream, ServerCallContext context)
         {
             if (request.Limit <= 0)
             {
@@ -72,7 +72,7 @@ namespace Math
             }
         }
 
-        public async Task<Num> Sum(IAsyncStreamReader<Num> requestStream, ServerCallContext context)
+        public override async Task<Num> Sum(IAsyncStreamReader<Num> requestStream, ServerCallContext context)
         {
             long sum = 0;
             await requestStream.ForEachAsync(async num =>
@@ -82,7 +82,7 @@ namespace Math
             return new Num { Num_ = sum };
         }
 
-        public async Task DivMany(IAsyncStreamReader<DivArgs> requestStream, IServerStreamWriter<DivReply> responseStream, ServerCallContext context)
+        public override async Task DivMany(IAsyncStreamReader<DivArgs> requestStream, IServerStreamWriter<DivReply> responseStream, ServerCallContext context)
         {
             await requestStream.ForEachAsync(async divArgs => await responseStream.WriteAsync(DivInternal(divArgs)));
         }
diff --git a/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
index 8318e84277c91d621754d1cb2f963dafc8586a5e..fb292945a6e626f594e3b873ac3bf0bbc6ecf3d7 100644
--- a/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
+++ b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
@@ -49,7 +49,7 @@ namespace Grpc.HealthCheck.Tests
         const string Host = "localhost";
         Server server;
         Channel channel;
-        Grpc.Health.V1.Health.IHealthClient client;
+        Grpc.Health.V1.Health.HealthClient client;
         Grpc.HealthCheck.HealthServiceImpl serviceImpl;
 
         [TestFixtureSetUp]
diff --git a/src/csharp/Grpc.HealthCheck/HealthGrpc.cs b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs
index 68320eb5c2effa410693eec82851005105a854b7..72e11cca3a1d4b3709a500fa2320036a22ab5f78 100644
--- a/src/csharp/Grpc.HealthCheck/HealthGrpc.cs
+++ b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs
@@ -29,6 +29,7 @@ namespace Grpc.Health.V1 {
     }
 
     // client interface
+    [System.Obsolete("Client side interfaced will be removed in the next release. Use client class directly.")]
     public interface IHealthClient
     {
       global::Grpc.Health.V1.HealthCheckResponse Check(global::Grpc.Health.V1.HealthCheckRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
@@ -38,36 +39,59 @@ namespace Grpc.Health.V1 {
     }
 
     // server-side interface
+    [System.Obsolete("Service implementations should inherit from the generated abstract base class instead.")]
     public interface IHealth
     {
       Task<global::Grpc.Health.V1.HealthCheckResponse> Check(global::Grpc.Health.V1.HealthCheckRequest request, ServerCallContext context);
     }
 
+    // server-side abstract class
+    public abstract class HealthBase
+    {
+      public virtual Task<global::Grpc.Health.V1.HealthCheckResponse> Check(global::Grpc.Health.V1.HealthCheckRequest request, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+    }
+
     // client stub
-    public class HealthClient : ClientBase, IHealthClient
+    public class HealthClient : ClientBase<HealthClient>, IHealthClient
     {
       public HealthClient(Channel channel) : base(channel)
       {
       }
-      public global::Grpc.Health.V1.HealthCheckResponse Check(global::Grpc.Health.V1.HealthCheckRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public HealthClient(CallInvoker callInvoker) : base(callInvoker)
       {
-        var call = CreateCall(__Method_Check, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.BlockingUnaryCall(call, request);
       }
-      public global::Grpc.Health.V1.HealthCheckResponse Check(global::Grpc.Health.V1.HealthCheckRequest request, CallOptions options)
+      ///<summary>Protected parameterless constructor to allow creation of test doubles.</summary>
+      protected HealthClient() : base()
+      {
+      }
+      ///<summary>Protected constructor to allow creation of configured clients.</summary>
+      protected HealthClient(ClientBaseConfiguration configuration) : base(configuration)
+      {
+      }
+
+      public virtual global::Grpc.Health.V1.HealthCheckResponse Check(global::Grpc.Health.V1.HealthCheckRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_Check, options);
-        return Calls.BlockingUnaryCall(call, request);
+        return Check(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncUnaryCall<global::Grpc.Health.V1.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1.HealthCheckRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual global::Grpc.Health.V1.HealthCheckResponse Check(global::Grpc.Health.V1.HealthCheckRequest request, CallOptions options)
       {
-        var call = CreateCall(__Method_Check, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncUnaryCall(call, request);
+        return CallInvoker.BlockingUnaryCall(__Method_Check, null, options, request);
       }
-      public AsyncUnaryCall<global::Grpc.Health.V1.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1.HealthCheckRequest request, CallOptions options)
+      public virtual AsyncUnaryCall<global::Grpc.Health.V1.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1.HealthCheckRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_Check, options);
-        return Calls.AsyncUnaryCall(call, request);
+        return CheckAsync(request, new CallOptions(headers, deadline, cancellationToken));
+      }
+      public virtual AsyncUnaryCall<global::Grpc.Health.V1.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1.HealthCheckRequest request, CallOptions options)
+      {
+        return CallInvoker.AsyncUnaryCall(__Method_Check, null, options, request);
+      }
+      protected override HealthClient NewInstance(ClientBaseConfiguration configuration)
+      {
+        return new HealthClient(configuration);
       }
     }
 
@@ -78,6 +102,13 @@ namespace Grpc.Health.V1 {
           .AddMethod(__Method_Check, serviceImpl.Check).Build();
     }
 
+    // creates service definition that can be registered with a server
+    public static ServerServiceDefinition BindService(HealthBase serviceImpl)
+    {
+      return ServerServiceDefinition.CreateBuilder(__ServiceName)
+          .AddMethod(__Method_Check, serviceImpl.Check).Build();
+    }
+
     // creates a new client
     public static HealthClient NewClient(Channel channel)
     {
diff --git a/src/csharp/Grpc.HealthCheck/HealthServiceImpl.cs b/src/csharp/Grpc.HealthCheck/HealthServiceImpl.cs
index a6e82aa0d4dc59be23db77a3aaf0657e318a415c..d0406ece00616ca308ac7865c37125aa83240983 100644
--- a/src/csharp/Grpc.HealthCheck/HealthServiceImpl.cs
+++ b/src/csharp/Grpc.HealthCheck/HealthServiceImpl.cs
@@ -51,7 +51,7 @@ namespace Grpc.HealthCheck
     /// server.AddServiceDefinition(Grpc.Health.V1.Health.BindService(serviceImpl));
     /// </code>
     /// </summary>
-    public class HealthServiceImpl : Grpc.Health.V1.Health.IHealth
+    public class HealthServiceImpl : Grpc.Health.V1.Health.HealthBase
     {
         private readonly object myLock = new object();
         private readonly Dictionary<string, HealthCheckResponse.Types.ServingStatus> statusMap = 
@@ -99,7 +99,7 @@ namespace Grpc.HealthCheck
         /// <param name="request">The check request.</param>
         /// <param name="context">The call context.</param>
         /// <returns>The asynchronous response.</returns>
-        public Task<HealthCheckResponse> Check(HealthCheckRequest request, ServerCallContext context)
+        public override Task<HealthCheckResponse> Check(HealthCheckRequest request, ServerCallContext context)
         {
             lock (myLock)
             {
diff --git a/src/csharp/Grpc.IntegrationTesting/BenchmarkServiceImpl.cs b/src/csharp/Grpc.IntegrationTesting/BenchmarkServiceImpl.cs
index 7e7bc713a031a87843719b3cf987edf3dcafbdd5..07f2703d4a4ee06427032c257de331915e8326fb 100644
--- a/src/csharp/Grpc.IntegrationTesting/BenchmarkServiceImpl.cs
+++ b/src/csharp/Grpc.IntegrationTesting/BenchmarkServiceImpl.cs
@@ -1,6 +1,6 @@
 #region Copyright notice and license
 
-// Copyright 2015, Google Inc.
+// Copyright 2015-2016, Google Inc.
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without
@@ -44,19 +44,19 @@ namespace Grpc.Testing
     /// <summary>
     /// Implementation of BenchmarkService server
     /// </summary>
-    public class BenchmarkServiceImpl : BenchmarkService.IBenchmarkService
+    public class BenchmarkServiceImpl : BenchmarkService.BenchmarkServiceBase
     {
         public BenchmarkServiceImpl()
         {
         }
 
-        public Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context)
+        public override Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context)
         {
             var response = new SimpleResponse { Payload = CreateZerosPayload(request.ResponseSize) };
             return Task.FromResult(response);
         }
 
-        public async Task StreamingCall(IAsyncStreamReader<SimpleRequest> requestStream, IServerStreamWriter<SimpleResponse> responseStream, ServerCallContext context)
+        public override async Task StreamingCall(IAsyncStreamReader<SimpleRequest> requestStream, IServerStreamWriter<SimpleResponse> responseStream, ServerCallContext context)
         {
             await requestStream.ForEachAsync(async request =>
             {
diff --git a/src/csharp/Grpc.IntegrationTesting/ClientRunners.cs b/src/csharp/Grpc.IntegrationTesting/ClientRunners.cs
index 5bfc89d5914ce20ed3e4e69388611c2f6de40eab..0bcacf76e507bae163c1f879f7bb384083ab1fed 100644
--- a/src/csharp/Grpc.IntegrationTesting/ClientRunners.cs
+++ b/src/csharp/Grpc.IntegrationTesting/ClientRunners.cs
@@ -112,7 +112,7 @@ namespace Grpc.IntegrationTesting
         readonly PayloadConfig payloadConfig;
         readonly Histogram histogram;
 
-        readonly BenchmarkService.IBenchmarkServiceClient client;
+        readonly BenchmarkService.BenchmarkServiceClient client;
         readonly Task runnerTask;
         readonly CancellationTokenSource stoppedCts;
         readonly WallClockStopwatch wallClockStopwatch = new WallClockStopwatch();
diff --git a/src/csharp/Grpc.IntegrationTesting/GeneratedClientTest.cs b/src/csharp/Grpc.IntegrationTesting/GeneratedClientTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..37786b6c30a79eb8be5eb2b969f0e0e672b01fb9
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting/GeneratedClientTest.cs
@@ -0,0 +1,106 @@
+#region Copyright notice and license
+
+// Copyright 2015-2016, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Utils;
+using Grpc.Testing;
+using Moq;
+using NUnit.Framework;
+
+namespace Grpc.IntegrationTesting
+{
+    public class GeneratedClientTest
+    {
+        TestService.TestServiceClient unimplementedClient = new UnimplementedTestServiceClient();
+
+        [Test]
+        public void ExpandedParamOverloadCanBeMocked()
+        {
+            var expected = new SimpleResponse();
+
+            var mockClient = new Mock<TestService.TestServiceClient>();
+            // mocking is relatively clumsy because one needs to specify value for all the optional params.
+            mockClient.Setup(m => m.UnaryCall(It.IsAny<SimpleRequest>(), null, null, CancellationToken.None)).Returns(expected);
+
+            Assert.AreSame(expected, mockClient.Object.UnaryCall(new SimpleRequest()));
+        }
+
+        [Test]
+        public void CallOptionsOverloadCanBeMocked()
+        {
+            var expected = new SimpleResponse();
+
+            var mockClient = new Mock<TestService.TestServiceClient>();
+            mockClient.Setup(m => m.UnaryCall(It.IsAny<SimpleRequest>(), It.IsAny<CallOptions>())).Returns(expected);
+
+            Assert.AreSame(expected, mockClient.Object.UnaryCall(new SimpleRequest(), new CallOptions()));
+        }
+
+        [Test]
+        public void DefaultMethodStubThrows_UnaryCall()
+        {
+            Assert.Throws(typeof(NotImplementedException), () => unimplementedClient.UnaryCall(new SimpleRequest()));
+        }
+
+        [Test]
+        public void DefaultMethodStubThrows_ClientStreaming()
+        {
+            Assert.Throws(typeof(NotImplementedException), () => unimplementedClient.StreamingInputCall());
+        }
+
+        [Test]
+        public void DefaultMethodStubThrows_ServerStreaming()
+        {
+            Assert.Throws(typeof(NotImplementedException), () => unimplementedClient.StreamingOutputCall(new StreamingOutputCallRequest()));
+        }
+
+        [Test]
+        public void DefaultMethodStubThrows_DuplexStreaming()
+        {
+            Assert.Throws(typeof(NotImplementedException), () => unimplementedClient.FullDuplexCall());
+        }
+
+        /// <summary>
+        /// Subclass of the generated client that doesn't override any method stubs.
+        /// </summary>
+        private class UnimplementedTestServiceClient : TestService.TestServiceClient
+        {
+        }
+    }
+}
diff --git a/src/csharp/Grpc.IntegrationTesting/GeneratedServiceBaseTest.cs b/src/csharp/Grpc.IntegrationTesting/GeneratedServiceBaseTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..99aa7290301ab221117e046048cb49e211133ac9
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting/GeneratedServiceBaseTest.cs
@@ -0,0 +1,116 @@
+#region Copyright notice and license
+
+// Copyright 2015-2016, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Utils;
+using Grpc.Testing;
+using Moq;
+using NUnit.Framework;
+
+namespace Grpc.IntegrationTesting
+{
+    public class GeneratedServiceBaseTest
+    {
+        const string Host = "localhost";
+        Server server;
+        Channel channel;
+        TestService.TestServiceClient client;
+
+        [SetUp]
+        public void Init()
+        {
+            server = new Server
+            {
+                Services = { TestService.BindService(new UnimplementedTestServiceImpl()) },
+                Ports = { { Host, ServerPort.PickUnused, SslServerCredentials.Insecure } }
+            };
+            server.Start();
+            channel = new Channel(Host, server.Ports.Single().BoundPort, ChannelCredentials.Insecure);
+            client = TestService.NewClient(channel);
+        }
+
+        [TearDown]
+        public void Cleanup()
+        {
+            channel.ShutdownAsync().Wait();
+            server.ShutdownAsync().Wait();
+        }
+
+        [Test]
+        public void UnimplementedByDefault_Unary()
+        {
+            var ex = Assert.Throws<RpcException>(() => client.UnaryCall(new SimpleRequest { }));
+            Assert.AreEqual(StatusCode.Unimplemented, ex.Status.StatusCode);
+        }
+
+        [Test]
+        public async Task UnimplementedByDefault_ClientStreaming()
+        {
+            var call = client.StreamingInputCall();
+
+            var ex = Assert.Throws<RpcException>(async () => await call);
+            Assert.AreEqual(StatusCode.Unimplemented, ex.Status.StatusCode);
+        }
+
+        [Test]
+        public async Task UnimplementedByDefault_ServerStreamingCall()
+        {
+            var call = client.StreamingOutputCall(new StreamingOutputCallRequest());
+
+            var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.MoveNext());
+            Assert.AreEqual(StatusCode.Unimplemented, ex.Status.StatusCode);
+        }
+
+        [Test]
+        public async Task UnimplementedByDefault_DuplexStreamingCall()
+        {
+            var call = client.FullDuplexCall();
+
+            var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.MoveNext());
+            Assert.AreEqual(StatusCode.Unimplemented, ex.Status.StatusCode);
+        }
+
+        /// <summary>
+        /// Implementation of TestService that doesn't override any methods.
+        /// </summary>
+        private class UnimplementedTestServiceImpl : TestService.TestServiceBase
+        {
+        }
+    }
+}
diff --git a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
index d3c572fe279e3a4d725f617af3c36fd7eb919b85..64d14b0df55834adcc11601716d2de4a7c0826c2 100644
--- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
+++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
@@ -84,7 +84,6 @@
     <Compile Include="..\Grpc.Core\Version.cs">
       <Link>Version.cs</Link>
     </Compile>
-    <Compile Include="HeaderInterceptorTest.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Empty.cs" />
     <Compile Include="Messages.cs" />
@@ -114,6 +113,8 @@
     <Compile Include="QpsWorker.cs" />
     <Compile Include="WallClockStopwatch.cs" />
     <Compile Include="GenericService.cs" />
+    <Compile Include="GeneratedServiceBaseTest.cs" />
+    <Compile Include="GeneratedClientTest.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup>
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
index b0e33e49f77eefabf6a80ada6e0b22e2ab6f2fae..7ca522193607b7b24f94454e90f3656b336af367 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
@@ -1,6 +1,6 @@
 #region Copyright notice and license
 
-// Copyright 2015, Google Inc.
+// Copyright 2015-2016, Google Inc.
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without
@@ -217,7 +217,7 @@ namespace Grpc.IntegrationTesting
             }
         }
 
-        public static void RunEmptyUnary(TestService.ITestServiceClient client)
+        public static void RunEmptyUnary(TestService.TestServiceClient client)
         {
             Console.WriteLine("running empty_unary");
             var response = client.EmptyCall(new Empty());
@@ -225,7 +225,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static void RunLargeUnary(TestService.ITestServiceClient client)
+        public static void RunLargeUnary(TestService.TestServiceClient client)
         {
             Console.WriteLine("running large_unary");
             var request = new SimpleRequest
@@ -241,7 +241,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static async Task RunClientStreamingAsync(TestService.ITestServiceClient client)
+        public static async Task RunClientStreamingAsync(TestService.TestServiceClient client)
         {
             Console.WriteLine("running client_streaming");
 
@@ -257,7 +257,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static async Task RunServerStreamingAsync(TestService.ITestServiceClient client)
+        public static async Task RunServerStreamingAsync(TestService.TestServiceClient client)
         {
             Console.WriteLine("running server_streaming");
 
@@ -281,7 +281,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static async Task RunPingPongAsync(TestService.ITestServiceClient client)
+        public static async Task RunPingPongAsync(TestService.TestServiceClient client)
         {
             Console.WriteLine("running ping_pong");
 
@@ -338,7 +338,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static async Task RunEmptyStreamAsync(TestService.ITestServiceClient client)
+        public static async Task RunEmptyStreamAsync(TestService.TestServiceClient client)
         {
             Console.WriteLine("running empty_stream");
             using (var call = client.FullDuplexCall())
@@ -434,7 +434,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static async Task RunCancelAfterBeginAsync(TestService.ITestServiceClient client)
+        public static async Task RunCancelAfterBeginAsync(TestService.TestServiceClient client)
         {
             Console.WriteLine("running cancel_after_begin");
 
@@ -451,7 +451,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static async Task RunCancelAfterFirstResponseAsync(TestService.ITestServiceClient client)
+        public static async Task RunCancelAfterFirstResponseAsync(TestService.TestServiceClient client)
         {
             Console.WriteLine("running cancel_after_first_response");
 
@@ -477,7 +477,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static async Task RunTimeoutOnSleepingServerAsync(TestService.ITestServiceClient client)
+        public static async Task RunTimeoutOnSleepingServerAsync(TestService.TestServiceClient client)
         {
             Console.WriteLine("running timeout_on_sleeping_server");
 
@@ -499,7 +499,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static async Task RunCustomMetadataAsync(TestService.ITestServiceClient client)
+        public static async Task RunCustomMetadataAsync(TestService.TestServiceClient client)
         {
             Console.WriteLine("running custom_metadata");
             {
@@ -546,7 +546,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static async Task RunStatusCodeAndMessageAsync(TestService.ITestServiceClient client)
+        public static async Task RunStatusCodeAndMessageAsync(TestService.TestServiceClient client)
         {
             Console.WriteLine("running status_code_and_message");
             var echoStatus = new EchoStatus
@@ -580,7 +580,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static void RunUnimplementedMethod(UnimplementedService.IUnimplementedServiceClient client)
+        public static void RunUnimplementedMethod(UnimplementedService.UnimplementedServiceClient client)
         {
             Console.WriteLine("running unimplemented_method");
             var e = Assert.Throws<RpcException>(() => client.UnimplementedCall(new Empty()));
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
index 5facb87971ec04964bfbe7088b7f4c0d34d87cd7..4ee1ff5ec8ae8f94a617a58c671d4dab104d1ea4 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
@@ -51,7 +51,7 @@ namespace Grpc.IntegrationTesting
         const string Host = "localhost";
         Server server;
         Channel channel;
-        TestService.ITestServiceClient client;
+        TestService.TestServiceClient client;
 
         [TestFixtureSetUp]
         public void Init()
diff --git a/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs
index 1c8bfed1f6fc225a689bd260d570176951405f4b..f95af5008f1c30c75729ce55bef7b4e6c31eb530 100644
--- a/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/MetadataCredentialsTest.cs
@@ -1,6 +1,6 @@
 #region Copyright notice and license
 
-// Copyright 2015, Google Inc.
+// Copyright 2015-2016, Google Inc.
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without
@@ -50,15 +50,15 @@ namespace Grpc.IntegrationTesting
         const string Host = "localhost";
         Server server;
         Channel channel;
-        TestService.ITestServiceClient client;
+        TestService.TestServiceClient client;
         List<ChannelOption> options;
-        Mock<TestService.ITestService> serviceMock;
+        Mock<TestService.TestServiceBase> serviceMock;
         AsyncAuthInterceptor asyncAuthInterceptor;
 
         [SetUp]
         public void Init()
         {
-            serviceMock = new Mock<TestService.ITestService>();
+            serviceMock = new Mock<TestService.TestServiceBase>();
             serviceMock.Setup(m => m.UnaryCall(It.IsAny<SimpleRequest>(), It.IsAny<ServerCallContext>()))
                 .Returns(new Func<SimpleRequest, ServerCallContext, Task<SimpleResponse>>(UnaryCallHandler));
 
diff --git a/src/csharp/Grpc.IntegrationTesting/ServicesGrpc.cs b/src/csharp/Grpc.IntegrationTesting/ServicesGrpc.cs
index 996439afbf1395f4d8f64ae3a47d20a43ef6998d..46b16cf202db82fd73f9df6ea455bcb0243dd55a 100644
--- a/src/csharp/Grpc.IntegrationTesting/ServicesGrpc.cs
+++ b/src/csharp/Grpc.IntegrationTesting/ServicesGrpc.cs
@@ -36,6 +36,7 @@ namespace Grpc.Testing {
     }
 
     // client interface
+    [System.Obsolete("Client side interfaced will be removed in the next release. Use client class directly.")]
     public interface IBenchmarkServiceClient
     {
       global::Grpc.Testing.SimpleResponse UnaryCall(global::Grpc.Testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
@@ -47,47 +48,73 @@ namespace Grpc.Testing {
     }
 
     // server-side interface
+    [System.Obsolete("Service implementations should inherit from the generated abstract base class instead.")]
     public interface IBenchmarkService
     {
       Task<global::Grpc.Testing.SimpleResponse> UnaryCall(global::Grpc.Testing.SimpleRequest request, ServerCallContext context);
       Task StreamingCall(IAsyncStreamReader<global::Grpc.Testing.SimpleRequest> requestStream, IServerStreamWriter<global::Grpc.Testing.SimpleResponse> responseStream, ServerCallContext context);
     }
 
+    // server-side abstract class
+    public abstract class BenchmarkServiceBase
+    {
+      public virtual Task<global::Grpc.Testing.SimpleResponse> UnaryCall(global::Grpc.Testing.SimpleRequest request, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task StreamingCall(IAsyncStreamReader<global::Grpc.Testing.SimpleRequest> requestStream, IServerStreamWriter<global::Grpc.Testing.SimpleResponse> responseStream, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+    }
+
     // client stub
-    public class BenchmarkServiceClient : ClientBase, IBenchmarkServiceClient
+    public class BenchmarkServiceClient : ClientBase<BenchmarkServiceClient>, IBenchmarkServiceClient
     {
       public BenchmarkServiceClient(Channel channel) : base(channel)
       {
       }
-      public global::Grpc.Testing.SimpleResponse UnaryCall(global::Grpc.Testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public BenchmarkServiceClient(CallInvoker callInvoker) : base(callInvoker)
+      {
+      }
+      ///<summary>Protected parameterless constructor to allow creation of test doubles.</summary>
+      protected BenchmarkServiceClient() : base()
       {
-        var call = CreateCall(__Method_UnaryCall, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.BlockingUnaryCall(call, request);
       }
-      public global::Grpc.Testing.SimpleResponse UnaryCall(global::Grpc.Testing.SimpleRequest request, CallOptions options)
+      ///<summary>Protected constructor to allow creation of configured clients.</summary>
+      protected BenchmarkServiceClient(ClientBaseConfiguration configuration) : base(configuration)
       {
-        var call = CreateCall(__Method_UnaryCall, options);
-        return Calls.BlockingUnaryCall(call, request);
       }
-      public AsyncUnaryCall<global::Grpc.Testing.SimpleResponse> UnaryCallAsync(global::Grpc.Testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+
+      public virtual global::Grpc.Testing.SimpleResponse UnaryCall(global::Grpc.Testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_UnaryCall, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncUnaryCall(call, request);
+        return UnaryCall(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncUnaryCall<global::Grpc.Testing.SimpleResponse> UnaryCallAsync(global::Grpc.Testing.SimpleRequest request, CallOptions options)
+      public virtual global::Grpc.Testing.SimpleResponse UnaryCall(global::Grpc.Testing.SimpleRequest request, CallOptions options)
       {
-        var call = CreateCall(__Method_UnaryCall, options);
-        return Calls.AsyncUnaryCall(call, request);
+        return CallInvoker.BlockingUnaryCall(__Method_UnaryCall, null, options, request);
       }
-      public AsyncDuplexStreamingCall<global::Grpc.Testing.SimpleRequest, global::Grpc.Testing.SimpleResponse> StreamingCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncUnaryCall<global::Grpc.Testing.SimpleResponse> UnaryCallAsync(global::Grpc.Testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_StreamingCall, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncDuplexStreamingCall(call);
+        return UnaryCallAsync(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncDuplexStreamingCall<global::Grpc.Testing.SimpleRequest, global::Grpc.Testing.SimpleResponse> StreamingCall(CallOptions options)
+      public virtual AsyncUnaryCall<global::Grpc.Testing.SimpleResponse> UnaryCallAsync(global::Grpc.Testing.SimpleRequest request, CallOptions options)
       {
-        var call = CreateCall(__Method_StreamingCall, options);
-        return Calls.AsyncDuplexStreamingCall(call);
+        return CallInvoker.AsyncUnaryCall(__Method_UnaryCall, null, options, request);
+      }
+      public virtual AsyncDuplexStreamingCall<global::Grpc.Testing.SimpleRequest, global::Grpc.Testing.SimpleResponse> StreamingCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        return StreamingCall(new CallOptions(headers, deadline, cancellationToken));
+      }
+      public virtual AsyncDuplexStreamingCall<global::Grpc.Testing.SimpleRequest, global::Grpc.Testing.SimpleResponse> StreamingCall(CallOptions options)
+      {
+        return CallInvoker.AsyncDuplexStreamingCall(__Method_StreamingCall, null, options);
+      }
+      protected override BenchmarkServiceClient NewInstance(ClientBaseConfiguration configuration)
+      {
+        return new BenchmarkServiceClient(configuration);
       }
     }
 
@@ -99,6 +126,14 @@ namespace Grpc.Testing {
           .AddMethod(__Method_StreamingCall, serviceImpl.StreamingCall).Build();
     }
 
+    // creates service definition that can be registered with a server
+    public static ServerServiceDefinition BindService(BenchmarkServiceBase serviceImpl)
+    {
+      return ServerServiceDefinition.CreateBuilder(__ServiceName)
+          .AddMethod(__Method_UnaryCall, serviceImpl.UnaryCall)
+          .AddMethod(__Method_StreamingCall, serviceImpl.StreamingCall).Build();
+    }
+
     // creates a new client
     public static BenchmarkServiceClient NewClient(Channel channel)
     {
@@ -153,6 +188,7 @@ namespace Grpc.Testing {
     }
 
     // client interface
+    [System.Obsolete("Client side interfaced will be removed in the next release. Use client class directly.")]
     public interface IWorkerServiceClient
     {
       AsyncDuplexStreamingCall<global::Grpc.Testing.ServerArgs, global::Grpc.Testing.ServerStatus> RunServer(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
@@ -170,6 +206,7 @@ namespace Grpc.Testing {
     }
 
     // server-side interface
+    [System.Obsolete("Service implementations should inherit from the generated abstract base class instead.")]
     public interface IWorkerService
     {
       Task RunServer(IAsyncStreamReader<global::Grpc.Testing.ServerArgs> requestStream, IServerStreamWriter<global::Grpc.Testing.ServerStatus> responseStream, ServerCallContext context);
@@ -178,71 +215,100 @@ namespace Grpc.Testing {
       Task<global::Grpc.Testing.Void> QuitWorker(global::Grpc.Testing.Void request, ServerCallContext context);
     }
 
+    // server-side abstract class
+    public abstract class WorkerServiceBase
+    {
+      public virtual Task RunServer(IAsyncStreamReader<global::Grpc.Testing.ServerArgs> requestStream, IServerStreamWriter<global::Grpc.Testing.ServerStatus> responseStream, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task RunClient(IAsyncStreamReader<global::Grpc.Testing.ClientArgs> requestStream, IServerStreamWriter<global::Grpc.Testing.ClientStatus> responseStream, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task<global::Grpc.Testing.CoreResponse> CoreCount(global::Grpc.Testing.CoreRequest request, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task<global::Grpc.Testing.Void> QuitWorker(global::Grpc.Testing.Void request, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+    }
+
     // client stub
-    public class WorkerServiceClient : ClientBase, IWorkerServiceClient
+    public class WorkerServiceClient : ClientBase<WorkerServiceClient>, IWorkerServiceClient
     {
       public WorkerServiceClient(Channel channel) : base(channel)
       {
       }
-      public AsyncDuplexStreamingCall<global::Grpc.Testing.ServerArgs, global::Grpc.Testing.ServerStatus> RunServer(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public WorkerServiceClient(CallInvoker callInvoker) : base(callInvoker)
       {
-        var call = CreateCall(__Method_RunServer, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncDuplexStreamingCall(call);
       }
-      public AsyncDuplexStreamingCall<global::Grpc.Testing.ServerArgs, global::Grpc.Testing.ServerStatus> RunServer(CallOptions options)
+      ///<summary>Protected parameterless constructor to allow creation of test doubles.</summary>
+      protected WorkerServiceClient() : base()
       {
-        var call = CreateCall(__Method_RunServer, options);
-        return Calls.AsyncDuplexStreamingCall(call);
       }
-      public AsyncDuplexStreamingCall<global::Grpc.Testing.ClientArgs, global::Grpc.Testing.ClientStatus> RunClient(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      ///<summary>Protected constructor to allow creation of configured clients.</summary>
+      protected WorkerServiceClient(ClientBaseConfiguration configuration) : base(configuration)
       {
-        var call = CreateCall(__Method_RunClient, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncDuplexStreamingCall(call);
       }
-      public AsyncDuplexStreamingCall<global::Grpc.Testing.ClientArgs, global::Grpc.Testing.ClientStatus> RunClient(CallOptions options)
+
+      public virtual AsyncDuplexStreamingCall<global::Grpc.Testing.ServerArgs, global::Grpc.Testing.ServerStatus> RunServer(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_RunClient, options);
-        return Calls.AsyncDuplexStreamingCall(call);
+        return RunServer(new CallOptions(headers, deadline, cancellationToken));
       }
-      public global::Grpc.Testing.CoreResponse CoreCount(global::Grpc.Testing.CoreRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncDuplexStreamingCall<global::Grpc.Testing.ServerArgs, global::Grpc.Testing.ServerStatus> RunServer(CallOptions options)
       {
-        var call = CreateCall(__Method_CoreCount, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.BlockingUnaryCall(call, request);
+        return CallInvoker.AsyncDuplexStreamingCall(__Method_RunServer, null, options);
       }
-      public global::Grpc.Testing.CoreResponse CoreCount(global::Grpc.Testing.CoreRequest request, CallOptions options)
+      public virtual AsyncDuplexStreamingCall<global::Grpc.Testing.ClientArgs, global::Grpc.Testing.ClientStatus> RunClient(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_CoreCount, options);
-        return Calls.BlockingUnaryCall(call, request);
+        return RunClient(new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncUnaryCall<global::Grpc.Testing.CoreResponse> CoreCountAsync(global::Grpc.Testing.CoreRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncDuplexStreamingCall<global::Grpc.Testing.ClientArgs, global::Grpc.Testing.ClientStatus> RunClient(CallOptions options)
       {
-        var call = CreateCall(__Method_CoreCount, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncUnaryCall(call, request);
+        return CallInvoker.AsyncDuplexStreamingCall(__Method_RunClient, null, options);
       }
-      public AsyncUnaryCall<global::Grpc.Testing.CoreResponse> CoreCountAsync(global::Grpc.Testing.CoreRequest request, CallOptions options)
+      public virtual global::Grpc.Testing.CoreResponse CoreCount(global::Grpc.Testing.CoreRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_CoreCount, options);
-        return Calls.AsyncUnaryCall(call, request);
+        return CoreCount(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public global::Grpc.Testing.Void QuitWorker(global::Grpc.Testing.Void request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual global::Grpc.Testing.CoreResponse CoreCount(global::Grpc.Testing.CoreRequest request, CallOptions options)
       {
-        var call = CreateCall(__Method_QuitWorker, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.BlockingUnaryCall(call, request);
+        return CallInvoker.BlockingUnaryCall(__Method_CoreCount, null, options, request);
       }
-      public global::Grpc.Testing.Void QuitWorker(global::Grpc.Testing.Void request, CallOptions options)
+      public virtual AsyncUnaryCall<global::Grpc.Testing.CoreResponse> CoreCountAsync(global::Grpc.Testing.CoreRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_QuitWorker, options);
-        return Calls.BlockingUnaryCall(call, request);
+        return CoreCountAsync(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncUnaryCall<global::Grpc.Testing.Void> QuitWorkerAsync(global::Grpc.Testing.Void request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncUnaryCall<global::Grpc.Testing.CoreResponse> CoreCountAsync(global::Grpc.Testing.CoreRequest request, CallOptions options)
       {
-        var call = CreateCall(__Method_QuitWorker, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncUnaryCall(call, request);
+        return CallInvoker.AsyncUnaryCall(__Method_CoreCount, null, options, request);
       }
-      public AsyncUnaryCall<global::Grpc.Testing.Void> QuitWorkerAsync(global::Grpc.Testing.Void request, CallOptions options)
+      public virtual global::Grpc.Testing.Void QuitWorker(global::Grpc.Testing.Void request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_QuitWorker, options);
-        return Calls.AsyncUnaryCall(call, request);
+        return QuitWorker(request, new CallOptions(headers, deadline, cancellationToken));
+      }
+      public virtual global::Grpc.Testing.Void QuitWorker(global::Grpc.Testing.Void request, CallOptions options)
+      {
+        return CallInvoker.BlockingUnaryCall(__Method_QuitWorker, null, options, request);
+      }
+      public virtual AsyncUnaryCall<global::Grpc.Testing.Void> QuitWorkerAsync(global::Grpc.Testing.Void request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        return QuitWorkerAsync(request, new CallOptions(headers, deadline, cancellationToken));
+      }
+      public virtual AsyncUnaryCall<global::Grpc.Testing.Void> QuitWorkerAsync(global::Grpc.Testing.Void request, CallOptions options)
+      {
+        return CallInvoker.AsyncUnaryCall(__Method_QuitWorker, null, options, request);
+      }
+      protected override WorkerServiceClient NewInstance(ClientBaseConfiguration configuration)
+      {
+        return new WorkerServiceClient(configuration);
       }
     }
 
@@ -256,6 +322,16 @@ namespace Grpc.Testing {
           .AddMethod(__Method_QuitWorker, serviceImpl.QuitWorker).Build();
     }
 
+    // creates service definition that can be registered with a server
+    public static ServerServiceDefinition BindService(WorkerServiceBase serviceImpl)
+    {
+      return ServerServiceDefinition.CreateBuilder(__ServiceName)
+          .AddMethod(__Method_RunServer, serviceImpl.RunServer)
+          .AddMethod(__Method_RunClient, serviceImpl.RunClient)
+          .AddMethod(__Method_CoreCount, serviceImpl.CoreCount)
+          .AddMethod(__Method_QuitWorker, serviceImpl.QuitWorker).Build();
+    }
+
     // creates a new client
     public static WorkerServiceClient NewClient(Channel channel)
     {
diff --git a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
index 37b2518c21e7e346f011498e6356f3bae2ddaa6f..3df45b5f7084f5748a587e8f2b2ecd5b3adbdf86 100644
--- a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
@@ -1,6 +1,6 @@
 #region Copyright notice and license
 
-// Copyright 2015, Google Inc.
+// Copyright 2015-2016, Google Inc.
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without
@@ -53,7 +53,7 @@ namespace Grpc.IntegrationTesting
         const string Host = "localhost";
         Server server;
         Channel channel;
-        TestService.ITestServiceClient client;
+        TestService.TestServiceClient client;
 
         [TestFixtureSetUp]
         public void Init()
diff --git a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
index 2c469080d9df2f5d3408393fc77d486abdffba47..b84ec2d984ade80536ce30564e247fe748df2901 100644
--- a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
+++ b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
@@ -69,6 +69,7 @@ namespace Grpc.Testing {
     }
 
     // client interface
+    [System.Obsolete("Client side interfaced will be removed in the next release. Use client class directly.")]
     public interface ITestServiceClient
     {
       global::Grpc.Testing.Empty EmptyCall(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
@@ -90,6 +91,7 @@ namespace Grpc.Testing {
     }
 
     // server-side interface
+    [System.Obsolete("Service implementations should inherit from the generated abstract base class instead.")]
     public interface ITestService
     {
       Task<global::Grpc.Testing.Empty> EmptyCall(global::Grpc.Testing.Empty request, ServerCallContext context);
@@ -100,91 +102,126 @@ namespace Grpc.Testing {
       Task HalfDuplexCall(IAsyncStreamReader<global::Grpc.Testing.StreamingOutputCallRequest> requestStream, IServerStreamWriter<global::Grpc.Testing.StreamingOutputCallResponse> responseStream, ServerCallContext context);
     }
 
+    // server-side abstract class
+    public abstract class TestServiceBase
+    {
+      public virtual Task<global::Grpc.Testing.Empty> EmptyCall(global::Grpc.Testing.Empty request, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task<global::Grpc.Testing.SimpleResponse> UnaryCall(global::Grpc.Testing.SimpleRequest request, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task StreamingOutputCall(global::Grpc.Testing.StreamingOutputCallRequest request, IServerStreamWriter<global::Grpc.Testing.StreamingOutputCallResponse> responseStream, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task<global::Grpc.Testing.StreamingInputCallResponse> StreamingInputCall(IAsyncStreamReader<global::Grpc.Testing.StreamingInputCallRequest> requestStream, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task FullDuplexCall(IAsyncStreamReader<global::Grpc.Testing.StreamingOutputCallRequest> requestStream, IServerStreamWriter<global::Grpc.Testing.StreamingOutputCallResponse> responseStream, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task HalfDuplexCall(IAsyncStreamReader<global::Grpc.Testing.StreamingOutputCallRequest> requestStream, IServerStreamWriter<global::Grpc.Testing.StreamingOutputCallResponse> responseStream, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+    }
+
     // client stub
-    public class TestServiceClient : ClientBase, ITestServiceClient
+    public class TestServiceClient : ClientBase<TestServiceClient>, ITestServiceClient
     {
       public TestServiceClient(Channel channel) : base(channel)
       {
       }
-      public global::Grpc.Testing.Empty EmptyCall(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public TestServiceClient(CallInvoker callInvoker) : base(callInvoker)
+      {
+      }
+      ///<summary>Protected parameterless constructor to allow creation of test doubles.</summary>
+      protected TestServiceClient() : base()
+      {
+      }
+      ///<summary>Protected constructor to allow creation of configured clients.</summary>
+      protected TestServiceClient(ClientBaseConfiguration configuration) : base(configuration)
+      {
+      }
+
+      public virtual global::Grpc.Testing.Empty EmptyCall(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_EmptyCall, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.BlockingUnaryCall(call, request);
+        return EmptyCall(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public global::Grpc.Testing.Empty EmptyCall(global::Grpc.Testing.Empty request, CallOptions options)
+      public virtual global::Grpc.Testing.Empty EmptyCall(global::Grpc.Testing.Empty request, CallOptions options)
       {
-        var call = CreateCall(__Method_EmptyCall, options);
-        return Calls.BlockingUnaryCall(call, request);
+        return CallInvoker.BlockingUnaryCall(__Method_EmptyCall, null, options, request);
       }
-      public AsyncUnaryCall<global::Grpc.Testing.Empty> EmptyCallAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncUnaryCall<global::Grpc.Testing.Empty> EmptyCallAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_EmptyCall, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncUnaryCall(call, request);
+        return EmptyCallAsync(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncUnaryCall<global::Grpc.Testing.Empty> EmptyCallAsync(global::Grpc.Testing.Empty request, CallOptions options)
+      public virtual AsyncUnaryCall<global::Grpc.Testing.Empty> EmptyCallAsync(global::Grpc.Testing.Empty request, CallOptions options)
       {
-        var call = CreateCall(__Method_EmptyCall, options);
-        return Calls.AsyncUnaryCall(call, request);
+        return CallInvoker.AsyncUnaryCall(__Method_EmptyCall, null, options, request);
       }
-      public global::Grpc.Testing.SimpleResponse UnaryCall(global::Grpc.Testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual global::Grpc.Testing.SimpleResponse UnaryCall(global::Grpc.Testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_UnaryCall, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.BlockingUnaryCall(call, request);
+        return UnaryCall(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public global::Grpc.Testing.SimpleResponse UnaryCall(global::Grpc.Testing.SimpleRequest request, CallOptions options)
+      public virtual global::Grpc.Testing.SimpleResponse UnaryCall(global::Grpc.Testing.SimpleRequest request, CallOptions options)
       {
-        var call = CreateCall(__Method_UnaryCall, options);
-        return Calls.BlockingUnaryCall(call, request);
+        return CallInvoker.BlockingUnaryCall(__Method_UnaryCall, null, options, request);
       }
-      public AsyncUnaryCall<global::Grpc.Testing.SimpleResponse> UnaryCallAsync(global::Grpc.Testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncUnaryCall<global::Grpc.Testing.SimpleResponse> UnaryCallAsync(global::Grpc.Testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_UnaryCall, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncUnaryCall(call, request);
+        return UnaryCallAsync(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncUnaryCall<global::Grpc.Testing.SimpleResponse> UnaryCallAsync(global::Grpc.Testing.SimpleRequest request, CallOptions options)
+      public virtual AsyncUnaryCall<global::Grpc.Testing.SimpleResponse> UnaryCallAsync(global::Grpc.Testing.SimpleRequest request, CallOptions options)
       {
-        var call = CreateCall(__Method_UnaryCall, options);
-        return Calls.AsyncUnaryCall(call, request);
+        return CallInvoker.AsyncUnaryCall(__Method_UnaryCall, null, options, request);
       }
-      public AsyncServerStreamingCall<global::Grpc.Testing.StreamingOutputCallResponse> StreamingOutputCall(global::Grpc.Testing.StreamingOutputCallRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncServerStreamingCall<global::Grpc.Testing.StreamingOutputCallResponse> StreamingOutputCall(global::Grpc.Testing.StreamingOutputCallRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_StreamingOutputCall, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncServerStreamingCall(call, request);
+        return StreamingOutputCall(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncServerStreamingCall<global::Grpc.Testing.StreamingOutputCallResponse> StreamingOutputCall(global::Grpc.Testing.StreamingOutputCallRequest request, CallOptions options)
+      public virtual AsyncServerStreamingCall<global::Grpc.Testing.StreamingOutputCallResponse> StreamingOutputCall(global::Grpc.Testing.StreamingOutputCallRequest request, CallOptions options)
       {
-        var call = CreateCall(__Method_StreamingOutputCall, options);
-        return Calls.AsyncServerStreamingCall(call, request);
+        return CallInvoker.AsyncServerStreamingCall(__Method_StreamingOutputCall, null, options, request);
       }
-      public AsyncClientStreamingCall<global::Grpc.Testing.StreamingInputCallRequest, global::Grpc.Testing.StreamingInputCallResponse> StreamingInputCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncClientStreamingCall<global::Grpc.Testing.StreamingInputCallRequest, global::Grpc.Testing.StreamingInputCallResponse> StreamingInputCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_StreamingInputCall, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncClientStreamingCall(call);
+        return StreamingInputCall(new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncClientStreamingCall<global::Grpc.Testing.StreamingInputCallRequest, global::Grpc.Testing.StreamingInputCallResponse> StreamingInputCall(CallOptions options)
+      public virtual AsyncClientStreamingCall<global::Grpc.Testing.StreamingInputCallRequest, global::Grpc.Testing.StreamingInputCallResponse> StreamingInputCall(CallOptions options)
       {
-        var call = CreateCall(__Method_StreamingInputCall, options);
-        return Calls.AsyncClientStreamingCall(call);
+        return CallInvoker.AsyncClientStreamingCall(__Method_StreamingInputCall, null, options);
       }
-      public AsyncDuplexStreamingCall<global::Grpc.Testing.StreamingOutputCallRequest, global::Grpc.Testing.StreamingOutputCallResponse> FullDuplexCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncDuplexStreamingCall<global::Grpc.Testing.StreamingOutputCallRequest, global::Grpc.Testing.StreamingOutputCallResponse> FullDuplexCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_FullDuplexCall, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncDuplexStreamingCall(call);
+        return FullDuplexCall(new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncDuplexStreamingCall<global::Grpc.Testing.StreamingOutputCallRequest, global::Grpc.Testing.StreamingOutputCallResponse> FullDuplexCall(CallOptions options)
+      public virtual AsyncDuplexStreamingCall<global::Grpc.Testing.StreamingOutputCallRequest, global::Grpc.Testing.StreamingOutputCallResponse> FullDuplexCall(CallOptions options)
       {
-        var call = CreateCall(__Method_FullDuplexCall, options);
-        return Calls.AsyncDuplexStreamingCall(call);
+        return CallInvoker.AsyncDuplexStreamingCall(__Method_FullDuplexCall, null, options);
       }
-      public AsyncDuplexStreamingCall<global::Grpc.Testing.StreamingOutputCallRequest, global::Grpc.Testing.StreamingOutputCallResponse> HalfDuplexCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncDuplexStreamingCall<global::Grpc.Testing.StreamingOutputCallRequest, global::Grpc.Testing.StreamingOutputCallResponse> HalfDuplexCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_HalfDuplexCall, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncDuplexStreamingCall(call);
+        return HalfDuplexCall(new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncDuplexStreamingCall<global::Grpc.Testing.StreamingOutputCallRequest, global::Grpc.Testing.StreamingOutputCallResponse> HalfDuplexCall(CallOptions options)
+      public virtual AsyncDuplexStreamingCall<global::Grpc.Testing.StreamingOutputCallRequest, global::Grpc.Testing.StreamingOutputCallResponse> HalfDuplexCall(CallOptions options)
       {
-        var call = CreateCall(__Method_HalfDuplexCall, options);
-        return Calls.AsyncDuplexStreamingCall(call);
+        return CallInvoker.AsyncDuplexStreamingCall(__Method_HalfDuplexCall, null, options);
+      }
+      protected override TestServiceClient NewInstance(ClientBaseConfiguration configuration)
+      {
+        return new TestServiceClient(configuration);
       }
     }
 
@@ -200,6 +237,18 @@ namespace Grpc.Testing {
           .AddMethod(__Method_HalfDuplexCall, serviceImpl.HalfDuplexCall).Build();
     }
 
+    // creates service definition that can be registered with a server
+    public static ServerServiceDefinition BindService(TestServiceBase serviceImpl)
+    {
+      return ServerServiceDefinition.CreateBuilder(__ServiceName)
+          .AddMethod(__Method_EmptyCall, serviceImpl.EmptyCall)
+          .AddMethod(__Method_UnaryCall, serviceImpl.UnaryCall)
+          .AddMethod(__Method_StreamingOutputCall, serviceImpl.StreamingOutputCall)
+          .AddMethod(__Method_StreamingInputCall, serviceImpl.StreamingInputCall)
+          .AddMethod(__Method_FullDuplexCall, serviceImpl.FullDuplexCall)
+          .AddMethod(__Method_HalfDuplexCall, serviceImpl.HalfDuplexCall).Build();
+    }
+
     // creates a new client
     public static TestServiceClient NewClient(Channel channel)
     {
@@ -227,6 +276,7 @@ namespace Grpc.Testing {
     }
 
     // client interface
+    [System.Obsolete("Client side interfaced will be removed in the next release. Use client class directly.")]
     public interface IUnimplementedServiceClient
     {
       global::Grpc.Testing.Empty UnimplementedCall(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
@@ -236,36 +286,59 @@ namespace Grpc.Testing {
     }
 
     // server-side interface
+    [System.Obsolete("Service implementations should inherit from the generated abstract base class instead.")]
     public interface IUnimplementedService
     {
       Task<global::Grpc.Testing.Empty> UnimplementedCall(global::Grpc.Testing.Empty request, ServerCallContext context);
     }
 
+    // server-side abstract class
+    public abstract class UnimplementedServiceBase
+    {
+      public virtual Task<global::Grpc.Testing.Empty> UnimplementedCall(global::Grpc.Testing.Empty request, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+    }
+
     // client stub
-    public class UnimplementedServiceClient : ClientBase, IUnimplementedServiceClient
+    public class UnimplementedServiceClient : ClientBase<UnimplementedServiceClient>, IUnimplementedServiceClient
     {
       public UnimplementedServiceClient(Channel channel) : base(channel)
       {
       }
-      public global::Grpc.Testing.Empty UnimplementedCall(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public UnimplementedServiceClient(CallInvoker callInvoker) : base(callInvoker)
+      {
+      }
+      ///<summary>Protected parameterless constructor to allow creation of test doubles.</summary>
+      protected UnimplementedServiceClient() : base()
+      {
+      }
+      ///<summary>Protected constructor to allow creation of configured clients.</summary>
+      protected UnimplementedServiceClient(ClientBaseConfiguration configuration) : base(configuration)
+      {
+      }
+
+      public virtual global::Grpc.Testing.Empty UnimplementedCall(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        return UnimplementedCall(request, new CallOptions(headers, deadline, cancellationToken));
+      }
+      public virtual global::Grpc.Testing.Empty UnimplementedCall(global::Grpc.Testing.Empty request, CallOptions options)
       {
-        var call = CreateCall(__Method_UnimplementedCall, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.BlockingUnaryCall(call, request);
+        return CallInvoker.BlockingUnaryCall(__Method_UnimplementedCall, null, options, request);
       }
-      public global::Grpc.Testing.Empty UnimplementedCall(global::Grpc.Testing.Empty request, CallOptions options)
+      public virtual AsyncUnaryCall<global::Grpc.Testing.Empty> UnimplementedCallAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_UnimplementedCall, options);
-        return Calls.BlockingUnaryCall(call, request);
+        return UnimplementedCallAsync(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncUnaryCall<global::Grpc.Testing.Empty> UnimplementedCallAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncUnaryCall<global::Grpc.Testing.Empty> UnimplementedCallAsync(global::Grpc.Testing.Empty request, CallOptions options)
       {
-        var call = CreateCall(__Method_UnimplementedCall, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncUnaryCall(call, request);
+        return CallInvoker.AsyncUnaryCall(__Method_UnimplementedCall, null, options, request);
       }
-      public AsyncUnaryCall<global::Grpc.Testing.Empty> UnimplementedCallAsync(global::Grpc.Testing.Empty request, CallOptions options)
+      protected override UnimplementedServiceClient NewInstance(ClientBaseConfiguration configuration)
       {
-        var call = CreateCall(__Method_UnimplementedCall, options);
-        return Calls.AsyncUnaryCall(call, request);
+        return new UnimplementedServiceClient(configuration);
       }
     }
 
@@ -276,6 +349,13 @@ namespace Grpc.Testing {
           .AddMethod(__Method_UnimplementedCall, serviceImpl.UnimplementedCall).Build();
     }
 
+    // creates service definition that can be registered with a server
+    public static ServerServiceDefinition BindService(UnimplementedServiceBase serviceImpl)
+    {
+      return ServerServiceDefinition.CreateBuilder(__ServiceName)
+          .AddMethod(__Method_UnimplementedCall, serviceImpl.UnimplementedCall).Build();
+    }
+
     // creates a new client
     public static UnimplementedServiceClient NewClient(Channel channel)
     {
@@ -311,6 +391,7 @@ namespace Grpc.Testing {
     }
 
     // client interface
+    [System.Obsolete("Client side interfaced will be removed in the next release. Use client class directly.")]
     public interface IReconnectServiceClient
     {
       global::Grpc.Testing.Empty Start(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
@@ -324,57 +405,81 @@ namespace Grpc.Testing {
     }
 
     // server-side interface
+    [System.Obsolete("Service implementations should inherit from the generated abstract base class instead.")]
     public interface IReconnectService
     {
       Task<global::Grpc.Testing.Empty> Start(global::Grpc.Testing.Empty request, ServerCallContext context);
       Task<global::Grpc.Testing.ReconnectInfo> Stop(global::Grpc.Testing.Empty request, ServerCallContext context);
     }
 
+    // server-side abstract class
+    public abstract class ReconnectServiceBase
+    {
+      public virtual Task<global::Grpc.Testing.Empty> Start(global::Grpc.Testing.Empty request, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+      public virtual Task<global::Grpc.Testing.ReconnectInfo> Stop(global::Grpc.Testing.Empty request, ServerCallContext context)
+      {
+        throw new RpcException(new Status(StatusCode.Unimplemented, ""));
+      }
+
+    }
+
     // client stub
-    public class ReconnectServiceClient : ClientBase, IReconnectServiceClient
+    public class ReconnectServiceClient : ClientBase<ReconnectServiceClient>, IReconnectServiceClient
     {
       public ReconnectServiceClient(Channel channel) : base(channel)
       {
       }
-      public global::Grpc.Testing.Empty Start(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public ReconnectServiceClient(CallInvoker callInvoker) : base(callInvoker)
       {
-        var call = CreateCall(__Method_Start, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.BlockingUnaryCall(call, request);
       }
-      public global::Grpc.Testing.Empty Start(global::Grpc.Testing.Empty request, CallOptions options)
+      ///<summary>Protected parameterless constructor to allow creation of test doubles.</summary>
+      protected ReconnectServiceClient() : base()
       {
-        var call = CreateCall(__Method_Start, options);
-        return Calls.BlockingUnaryCall(call, request);
       }
-      public AsyncUnaryCall<global::Grpc.Testing.Empty> StartAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      ///<summary>Protected constructor to allow creation of configured clients.</summary>
+      protected ReconnectServiceClient(ClientBaseConfiguration configuration) : base(configuration)
       {
-        var call = CreateCall(__Method_Start, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncUnaryCall(call, request);
       }
-      public AsyncUnaryCall<global::Grpc.Testing.Empty> StartAsync(global::Grpc.Testing.Empty request, CallOptions options)
+
+      public virtual global::Grpc.Testing.Empty Start(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_Start, options);
-        return Calls.AsyncUnaryCall(call, request);
+        return Start(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public global::Grpc.Testing.ReconnectInfo Stop(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual global::Grpc.Testing.Empty Start(global::Grpc.Testing.Empty request, CallOptions options)
       {
-        var call = CreateCall(__Method_Stop, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.BlockingUnaryCall(call, request);
+        return CallInvoker.BlockingUnaryCall(__Method_Start, null, options, request);
       }
-      public global::Grpc.Testing.ReconnectInfo Stop(global::Grpc.Testing.Empty request, CallOptions options)
+      public virtual AsyncUnaryCall<global::Grpc.Testing.Empty> StartAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_Stop, options);
-        return Calls.BlockingUnaryCall(call, request);
+        return StartAsync(request, new CallOptions(headers, deadline, cancellationToken));
       }
-      public AsyncUnaryCall<global::Grpc.Testing.ReconnectInfo> StopAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      public virtual AsyncUnaryCall<global::Grpc.Testing.Empty> StartAsync(global::Grpc.Testing.Empty request, CallOptions options)
       {
-        var call = CreateCall(__Method_Stop, new CallOptions(headers, deadline, cancellationToken));
-        return Calls.AsyncUnaryCall(call, request);
+        return CallInvoker.AsyncUnaryCall(__Method_Start, null, options, request);
       }
-      public AsyncUnaryCall<global::Grpc.Testing.ReconnectInfo> StopAsync(global::Grpc.Testing.Empty request, CallOptions options)
+      public virtual global::Grpc.Testing.ReconnectInfo Stop(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__Method_Stop, options);
-        return Calls.AsyncUnaryCall(call, request);
+        return Stop(request, new CallOptions(headers, deadline, cancellationToken));
+      }
+      public virtual global::Grpc.Testing.ReconnectInfo Stop(global::Grpc.Testing.Empty request, CallOptions options)
+      {
+        return CallInvoker.BlockingUnaryCall(__Method_Stop, null, options, request);
+      }
+      public virtual AsyncUnaryCall<global::Grpc.Testing.ReconnectInfo> StopAsync(global::Grpc.Testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+      {
+        return StopAsync(request, new CallOptions(headers, deadline, cancellationToken));
+      }
+      public virtual AsyncUnaryCall<global::Grpc.Testing.ReconnectInfo> StopAsync(global::Grpc.Testing.Empty request, CallOptions options)
+      {
+        return CallInvoker.AsyncUnaryCall(__Method_Stop, null, options, request);
+      }
+      protected override ReconnectServiceClient NewInstance(ClientBaseConfiguration configuration)
+      {
+        return new ReconnectServiceClient(configuration);
       }
     }
 
@@ -386,6 +491,14 @@ namespace Grpc.Testing {
           .AddMethod(__Method_Stop, serviceImpl.Stop).Build();
     }
 
+    // creates service definition that can be registered with a server
+    public static ServerServiceDefinition BindService(ReconnectServiceBase serviceImpl)
+    {
+      return ServerServiceDefinition.CreateBuilder(__ServiceName)
+          .AddMethod(__Method_Start, serviceImpl.Start)
+          .AddMethod(__Method_Stop, serviceImpl.Stop).Build();
+    }
+
     // creates a new client
     public static ReconnectServiceClient NewClient(Channel channel)
     {
diff --git a/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs b/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs
index 5a1b4cf319b696a57a0ec41badcdf6b2f598f280..354318e80e23d7996d9436eb784e7549af3e20d1 100644
--- a/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs
+++ b/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs
@@ -1,6 +1,6 @@
 #region Copyright notice and license
 
-// Copyright 2015, Google Inc.
+// Copyright 2015-2016, Google Inc.
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without
@@ -45,14 +45,14 @@ namespace Grpc.Testing
     /// <summary>
     /// Implementation of TestService server
     /// </summary>
-    public class TestServiceImpl : TestService.ITestService
+    public class TestServiceImpl : TestService.TestServiceBase
     {
-        public Task<Empty> EmptyCall(Empty request, ServerCallContext context)
+        public override Task<Empty> EmptyCall(Empty request, ServerCallContext context)
         {
             return Task.FromResult(new Empty());
         }
 
-        public async Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context)
+        public override async Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context)
         {
             await EnsureEchoMetadataAsync(context);
             EnsureEchoStatus(request.ResponseStatus, context);
@@ -61,7 +61,7 @@ namespace Grpc.Testing
             return response;
         }
 
-        public async Task StreamingOutputCall(StreamingOutputCallRequest request, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context)
+        public override async Task StreamingOutputCall(StreamingOutputCallRequest request, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context)
         {
             await EnsureEchoMetadataAsync(context);
             EnsureEchoStatus(request.ResponseStatus, context);
@@ -73,7 +73,7 @@ namespace Grpc.Testing
             }
         }
 
-        public async Task<StreamingInputCallResponse> StreamingInputCall(IAsyncStreamReader<StreamingInputCallRequest> requestStream, ServerCallContext context)
+        public override async Task<StreamingInputCallResponse> StreamingInputCall(IAsyncStreamReader<StreamingInputCallRequest> requestStream, ServerCallContext context)
         {
             await EnsureEchoMetadataAsync(context);
 
@@ -85,7 +85,7 @@ namespace Grpc.Testing
             return new StreamingInputCallResponse { AggregatedPayloadSize = sum };
         }
 
-        public async Task FullDuplexCall(IAsyncStreamReader<StreamingOutputCallRequest> requestStream, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context)
+        public override async Task FullDuplexCall(IAsyncStreamReader<StreamingOutputCallRequest> requestStream, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context)
         {
             await EnsureEchoMetadataAsync(context);
 
@@ -100,7 +100,7 @@ namespace Grpc.Testing
             });
         }
 
-        public async Task HalfDuplexCall(IAsyncStreamReader<StreamingOutputCallRequest> requestStream, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context)
+        public override async Task HalfDuplexCall(IAsyncStreamReader<StreamingOutputCallRequest> requestStream, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context)
         {
             throw new NotImplementedException();
         }
diff --git a/src/csharp/Grpc.IntegrationTesting/WorkerServiceImpl.cs b/src/csharp/Grpc.IntegrationTesting/WorkerServiceImpl.cs
index e5ad0caa033cf093a1c476342ca2d1f6b2b022d3..80dad9fdd9376a95af9efea1f733f9d8a81db06b 100644
--- a/src/csharp/Grpc.IntegrationTesting/WorkerServiceImpl.cs
+++ b/src/csharp/Grpc.IntegrationTesting/WorkerServiceImpl.cs
@@ -45,7 +45,7 @@ namespace Grpc.Testing
     /// <summary>
     /// Implementation of WorkerService server
     /// </summary>
-    public class WorkerServiceImpl : WorkerService.IWorkerService
+    public class WorkerServiceImpl : WorkerService.WorkerServiceBase
     {
         readonly Action stopRequestHandler;
 
@@ -54,7 +54,7 @@ namespace Grpc.Testing
             this.stopRequestHandler = GrpcPreconditions.CheckNotNull(stopRequestHandler);
         }
         
-        public async Task RunServer(IAsyncStreamReader<ServerArgs> requestStream, IServerStreamWriter<ServerStatus> responseStream, ServerCallContext context)
+        public override async Task RunServer(IAsyncStreamReader<ServerArgs> requestStream, IServerStreamWriter<ServerStatus> responseStream, ServerCallContext context)
         {
             GrpcPreconditions.CheckState(await requestStream.MoveNext());
             var serverConfig = requestStream.Current.Setup;
@@ -78,7 +78,7 @@ namespace Grpc.Testing
             await runner.StopAsync();
         }
 
-        public async Task RunClient(IAsyncStreamReader<ClientArgs> requestStream, IServerStreamWriter<ClientStatus> responseStream, ServerCallContext context)
+        public override async Task RunClient(IAsyncStreamReader<ClientArgs> requestStream, IServerStreamWriter<ClientStatus> responseStream, ServerCallContext context)
         {
             GrpcPreconditions.CheckState(await requestStream.MoveNext());
             var clientConfig = requestStream.Current.Setup;
@@ -100,12 +100,12 @@ namespace Grpc.Testing
             await runner.StopAsync();
         }
 
-        public Task<CoreResponse> CoreCount(CoreRequest request, ServerCallContext context)
+        public override Task<CoreResponse> CoreCount(CoreRequest request, ServerCallContext context)
         {
             return Task.FromResult(new CoreResponse { Cores = Environment.ProcessorCount });
         }
 
-        public Task<Void> QuitWorker(Void request, ServerCallContext context)
+        public override Task<Void> QuitWorker(Void request, ServerCallContext context)
         {
             stopRequestHandler();
             return Task.FromResult(new Void());
diff --git a/src/csharp/tests.json b/src/csharp/tests.json
index d66cb1b1008e44f2d71b3620d12f244260e6ced0..718bfa3287bba342d9858fb647d3ebf973a0a053 100644
--- a/src/csharp/tests.json
+++ b/src/csharp/tests.json
@@ -36,8 +36,9 @@
     "Math.Tests.MathClientServerTest",
     "Grpc.HealthCheck.Tests.HealthClientServerTest",
     "Grpc.HealthCheck.Tests.HealthServiceImplTest",
-    "Grpc.IntegrationTesting.HeaderInterceptorTest",
     "Grpc.IntegrationTesting.HistogramTest",
+    "Grpc.IntegrationTesting.GeneratedClientTest",
+    "Grpc.IntegrationTesting.GeneratedServiceBaseTest",
     "Grpc.IntegrationTesting.InteropClientServerTest",
     "Grpc.IntegrationTesting.MetadataCredentialsTest",
     "Grpc.IntegrationTesting.RunnerClientServerTest",