diff --git a/src/compiler/csharp_generator.cc b/src/compiler/csharp_generator.cc
index ccb0b688b6b4f63eba9524d36394c8291f6fa7e1..1910e9bd2dee7dc241bb49a4bba3061419df8342 100644
--- a/src/compiler/csharp_generator.cc
+++ b/src/compiler/csharp_generator.cc
@@ -257,7 +257,7 @@ void GenerateStaticMethodField(Printer* out, const MethodDescriptor *method) {
 }
 
 void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
-  out->Print("// client-side stub interface\n");
+  out->Print("// client interface\n");
   out->Print("public interface $name$\n", "name",
              GetClientInterfaceName(service));
   out->Print("{\n");
@@ -269,7 +269,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
     if (method_type == METHODTYPE_NO_STREAMING) {
       // unary calls have an extra synchronous stub method
       out->Print(
-          "$response$ $methodname$($request$ request, CancellationToken token = default(CancellationToken));\n",
+          "$response$ $methodname$($request$ request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));\n",
           "methodname", method->name(), "request",
           GetClassName(method->input_type()), "response",
           GetClassName(method->output_type()));
@@ -280,7 +280,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
       method_name += "Async";  // prevent name clash with synchronous method.
     }
     out->Print(
-        "$returntype$ $methodname$($request_maybe$CancellationToken token = default(CancellationToken));\n",
+        "$returntype$ $methodname$($request_maybe$Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));\n",
         "methodname", method_name, "request_maybe",
         GetMethodRequestParamMaybe(method), "returntype",
         GetMethodReturnTypeClient(method));
@@ -312,7 +312,7 @@ void GenerateServerInterface(Printer* out, const ServiceDescriptor *service) {
 void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
   out->Print("// client stub\n");
   out->Print(
-      "public class $name$ : AbstractStub<$name$, StubConfiguration>, $interface$\n",
+      "public class $name$ : ClientBase, $interface$\n",
       "name", GetClientClassName(service), "interface",
       GetClientInterfaceName(service));
   out->Print("{\n");
@@ -320,12 +320,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
 
   // constructors
   out->Print(
-      "public $name$(Channel channel) : this(channel, StubConfiguration.Default)\n",
-      "name", GetClientClassName(service));
-  out->Print("{\n");
-  out->Print("}\n");
-  out->Print(
-      "public $name$(Channel channel, StubConfiguration config) : base(channel, config)\n",
+      "public $name$(Channel channel) : base(channel)\n",
       "name", GetClientClassName(service));
   out->Print("{\n");
   out->Print("}\n");
@@ -337,16 +332,16 @@ 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, CancellationToken token = default(CancellationToken))\n",
+          "public $response$ $methodname$($request$ request, Metadata headers = 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($servicenamefield$, $methodfield$);\n",
+      out->Print("var call = CreateCall($servicenamefield$, $methodfield$, headers);\n",
                  "servicenamefield", GetServiceNameFieldName(), "methodfield",
                  GetMethodFieldName(method));
-      out->Print("return Calls.BlockingUnaryCall(call, request, token);\n");
+      out->Print("return Calls.BlockingUnaryCall(call, request, cancellationToken);\n");
       out->Outdent();
       out->Print("}\n");
     }
@@ -356,28 +351,28 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
       method_name += "Async";  // prevent name clash with synchronous method.
     }
     out->Print(
-        "public $returntype$ $methodname$($request_maybe$CancellationToken token = default(CancellationToken))\n",
+        "public $returntype$ $methodname$($request_maybe$Metadata headers = 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($servicenamefield$, $methodfield$);\n",
+    out->Print("var call = CreateCall($servicenamefield$, $methodfield$, headers);\n",
                "servicenamefield", GetServiceNameFieldName(), "methodfield",
                GetMethodFieldName(method));
     switch (GetMethodType(method)) {
       case METHODTYPE_NO_STREAMING:
-        out->Print("return Calls.AsyncUnaryCall(call, request, token);\n");
+        out->Print("return Calls.AsyncUnaryCall(call, request, cancellationToken);\n");
         break;
       case METHODTYPE_CLIENT_STREAMING:
-        out->Print("return Calls.AsyncClientStreamingCall(call, token);\n");
+        out->Print("return Calls.AsyncClientStreamingCall(call, cancellationToken);\n");
         break;
       case METHODTYPE_SERVER_STREAMING:
         out->Print(
-            "return Calls.AsyncServerStreamingCall(call, request, token);\n");
+            "return Calls.AsyncServerStreamingCall(call, request, cancellationToken);\n");
         break;
       case METHODTYPE_BIDI_STREAMING:
-        out->Print("return Calls.AsyncDuplexStreamingCall(call, token);\n");
+        out->Print("return Calls.AsyncDuplexStreamingCall(call, cancellationToken);\n");
         break;
       default:
         GOOGLE_LOG(FATAL)<< "Can't get here.";
@@ -423,9 +418,9 @@ void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor *service) {
 }
 
 void GenerateNewStubMethods(Printer* out, const ServiceDescriptor *service) {
-  out->Print("// creates a new client stub\n");
-  out->Print("public static $interface$ NewStub(Channel channel)\n",
-             "interface", GetClientInterfaceName(service));
+  out->Print("// creates a new client\n");
+  out->Print("public static $classname$ NewClient(Channel channel)\n",
+             "classname", GetClientClassName(service));
   out->Print("{\n");
   out->Indent();
   out->Print("return new $classname$(channel);\n", "classname",
@@ -433,17 +428,6 @@ void GenerateNewStubMethods(Printer* out, const ServiceDescriptor *service) {
   out->Outdent();
   out->Print("}\n");
   out->Print("\n");
-
-  out->Print("// creates a new client stub\n");
-  out->Print(
-      "public static $interface$ NewStub(Channel channel, StubConfiguration config)\n",
-      "interface", GetClientInterfaceName(service));
-  out->Print("{\n");
-  out->Indent();
-  out->Print("return new $classname$(channel, config);\n", "classname",
-             GetClientClassName(service));
-  out->Outdent();
-  out->Print("}\n");
 }
 
 void GenerateService(Printer* out, const ServiceDescriptor *service) {
diff --git a/src/csharp/Grpc.Auth/OAuth2InterceptorFactory.cs b/src/csharp/Grpc.Auth/OAuth2InterceptorFactory.cs
index ca384d1a6e444fe2f513fc84289d7d0d8eeefb56..420c4cb5371f67740223025d4428574ec0cdb0d8 100644
--- a/src/csharp/Grpc.Auth/OAuth2InterceptorFactory.cs
+++ b/src/csharp/Grpc.Auth/OAuth2InterceptorFactory.cs
@@ -52,10 +52,10 @@ namespace Grpc.Auth
         /// <summary>
         /// Creates OAuth2 interceptor.
         /// </summary>
-        public static HeaderInterceptorDelegate Create(GoogleCredential googleCredential)
+        public static MetadataInterceptorDelegate Create(GoogleCredential googleCredential)
         {
             var interceptor = new OAuth2Interceptor(googleCredential.InternalCredential, SystemClock.Default);
-            return new HeaderInterceptorDelegate(interceptor.InterceptHeaders);
+            return new MetadataInterceptorDelegate(interceptor.InterceptHeaders);
         }
 
         /// <summary>
@@ -94,10 +94,10 @@ namespace Grpc.Auth
                 return credential.Token.AccessToken;
             }
 
-            public void InterceptHeaders(Metadata.Builder headerBuilder)
+            public void InterceptHeaders(Metadata metadata)
             {
                 var accessToken = GetAccessToken(CancellationToken.None);
-                headerBuilder.Add(new Metadata.MetadataEntry(AuthorizationHeader, Schema + " " + accessToken));
+                metadata.Add(new Metadata.Entry(AuthorizationHeader, Schema + " " + accessToken));
             }
         }
     }
diff --git a/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs b/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs
index 2f6013483d98bc63b07149e1e836b6bb1695cb93..320423b245d2dd68ec175867efc21b6707c2d5a3 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs
@@ -44,17 +44,17 @@ namespace Grpc.Core.Internal.Tests
         [Test]
         public void CreateEmptyAndDestroy()
         {
-            var metadata = Metadata.CreateBuilder().Build();
-            var nativeMetadata = MetadataArraySafeHandle.Create(metadata);
+            var nativeMetadata = MetadataArraySafeHandle.Create(new Metadata());
             nativeMetadata.Dispose();
         }
 
         [Test]
         public void CreateAndDestroy()
         {
-            var metadata = Metadata.CreateBuilder()
-                .Add(new Metadata.MetadataEntry("host", "somehost"))
-                .Add(new Metadata.MetadataEntry("header2", "header value")).Build();
+            var metadata = new Metadata {
+                new Metadata.Entry("host", "somehost"),
+                new Metadata.Entry("header2", "header value"),
+            };
             var nativeMetadata = MetadataArraySafeHandle.Create(metadata);
             nativeMetadata.Dispose();
         }
diff --git a/src/csharp/Grpc.Core/Calls.cs b/src/csharp/Grpc.Core/Calls.cs
index 750282258f2ca358bfabf5d0994873594bf6dfdf..9e95182c7208c8713049a3cfeb0a9af1231c630d 100644
--- a/src/csharp/Grpc.Core/Calls.cs
+++ b/src/csharp/Grpc.Core/Calls.cs
@@ -39,7 +39,7 @@ using Grpc.Core.Internal;
 namespace Grpc.Core
 {
     /// <summary>
-    /// Helper methods for generated client stubs to make RPC calls.
+    /// Helper methods for generated clients to make RPC calls.
     /// </summary>
     public static class Calls
     {
diff --git a/src/csharp/Grpc.Core/Stub/AbstractStub.cs b/src/csharp/Grpc.Core/ClientBase.cs
similarity index 70%
rename from src/csharp/Grpc.Core/Stub/AbstractStub.cs
rename to src/csharp/Grpc.Core/ClientBase.cs
index 4a8b2543579f8e64b79c89b95d264ce30944b301..a099f96aeab4c31fceccf7203bc90f4e12a6f093 100644
--- a/src/csharp/Grpc.Core/Stub/AbstractStub.cs
+++ b/src/csharp/Grpc.Core/ClientBase.cs
@@ -32,26 +32,39 @@
 #endregion
 
 using System;
+using System.Collections.Generic;
+
 using Grpc.Core.Internal;
 
 namespace Grpc.Core
 {
-    // TODO: support adding timeout to methods.
+    public delegate void MetadataInterceptorDelegate(Metadata metadata);
+
     /// <summary>
-    /// Base for client-side stubs.
+    /// Base class for client-side stubs.
     /// </summary>
-    public abstract class AbstractStub<TStub, TConfig>
-        where TConfig : StubConfiguration
+    public abstract class ClientBase
     {
         readonly Channel channel;
-        readonly TConfig config;
 
-        public AbstractStub(Channel channel, TConfig config)
+        public ClientBase(Channel channel)
         {
             this.channel = channel;
-            this.config = config;
         }
 
+        /// <summary>
+        /// Can be used to register a custom header (initial metadata) interceptor.
+        /// The delegate each time before a new call on this client is started.
+        /// </summary>
+        public MetadataInterceptorDelegate HeaderInterceptor
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
+        /// Channel associated with this client.
+        /// </summary>
         public Channel Channel
         {
             get
@@ -63,13 +76,19 @@ namespace Grpc.Core
         /// <summary>
         /// Creates a new call to given method.
         /// </summary>
-        protected Call<TRequest, TResponse> CreateCall<TRequest, TResponse>(string serviceName, Method<TRequest, TResponse> method)
+        protected Call<TRequest, TResponse> CreateCall<TRequest, TResponse>(string serviceName, Method<TRequest, TResponse> method, Metadata metadata)
             where TRequest : class
             where TResponse : class
         {
-            var headerBuilder = Metadata.CreateBuilder();
-            config.HeaderInterceptor(headerBuilder);
-            return new Call<TRequest, TResponse>(serviceName, method, channel, headerBuilder.Build());
+            var interceptor = HeaderInterceptor;
+            if (interceptor != null)
+            {
+                metadata = metadata ?? new Metadata();
+                interceptor(metadata);
+                metadata.Freeze();
+            }
+            metadata = metadata ?? Metadata.Empty;
+            return new Call<TRequest, TResponse>(serviceName, method, channel, metadata);
         }
     }
 }
diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj
index cde42c3b7e26fe59c60696162df539014523f6db..a227fe547789761b1e461d687ecff06cdcd8024f 100644
--- a/src/csharp/Grpc.Core/Grpc.Core.csproj
+++ b/src/csharp/Grpc.Core/Grpc.Core.csproj
@@ -88,8 +88,7 @@
     <Compile Include="ServerCredentials.cs" />
     <Compile Include="Metadata.cs" />
     <Compile Include="Internal\MetadataArraySafeHandle.cs" />
-    <Compile Include="Stub\AbstractStub.cs" />
-    <Compile Include="Stub\StubConfiguration.cs" />
+    <Compile Include="ClientBase.cs" />
     <Compile Include="Internal\ServerCalls.cs" />
     <Compile Include="ServerMethods.cs" />
     <Compile Include="Internal\ClientRequestStream.cs" />
diff --git a/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs b/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs
index c9c4d954c91a8fc847b8b3c4c4ddeb9bf39dae6d..80aa7f5603464f8e2f62ca33c0d09c902cd05d7b 100644
--- a/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs
@@ -54,11 +54,11 @@ namespace Grpc.Core.Internal
 
         public static MetadataArraySafeHandle Create(Metadata metadata)
         {
-            var entries = metadata.Entries;
-            var metadataArray = grpcsharp_metadata_array_create(new UIntPtr((ulong)entries.Count));
-            for (int i = 0; i < entries.Count; i++)
+            // TODO(jtattermusch): we might wanna check that the metadata is readonly 
+            var metadataArray = grpcsharp_metadata_array_create(new UIntPtr((ulong)metadata.Count));
+            for (int i = 0; i < metadata.Count; i++)
             {
-                grpcsharp_metadata_array_add(metadataArray, entries[i].Key, entries[i].ValueBytes, new UIntPtr((ulong)entries[i].ValueBytes.Length));
+                grpcsharp_metadata_array_add(metadataArray, metadata[i].Key, metadata[i].ValueBytes, new UIntPtr((ulong)metadata[i].ValueBytes.Length));
             }
             return metadataArray;
         }
diff --git a/src/csharp/Grpc.Core/Metadata.cs b/src/csharp/Grpc.Core/Metadata.cs
index eccec26a616bf003c75b8ef57f114eea753d1cfa..4552d39d88e00143c88ae40631e612e04fb273e8 100644
--- a/src/csharp/Grpc.Core/Metadata.cs
+++ b/src/csharp/Grpc.Core/Metadata.cs
@@ -30,55 +30,163 @@
 #endregion
 
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Immutable;
+using System.Collections.Specialized;
 using System.Runtime.InteropServices;
 using System.Text;
 
+using Grpc.Core.Utils;
+
 namespace Grpc.Core
 {
     /// <summary>
-    /// gRPC call metadata.
+    /// Provides access to read and write metadata values to be exchanged during a call.
     /// </summary>
-    public class Metadata
+    public sealed class Metadata : IList<Metadata.Entry>
     {
-        public static readonly Metadata Empty = new Metadata(ImmutableList<MetadataEntry>.Empty);
+        /// <summary>
+        /// An read-only instance of metadata containing no entries.
+        /// </summary>
+        public static readonly Metadata Empty = new Metadata().Freeze();
+
+        readonly List<Entry> entries;
+        bool readOnly;
+
+        public Metadata()
+        {
+            this.entries = new List<Entry>();
+        }
+
+        public Metadata(ICollection<Entry> entries)
+        {
+            this.entries = new List<Entry>(entries);
+        }
+
+        /// <summary>
+        /// Makes this object read-only.
+        /// </summary>
+        /// <returns>this object</returns>
+        public Metadata Freeze()
+        {
+            this.readOnly = true;
+            return this;
+        }
+
+        // TODO: add support for access by key
+
+        #region IList members
+
+        public int IndexOf(Metadata.Entry item)
+        {
+            return entries.IndexOf(item);
+        }
 
-        readonly ImmutableList<MetadataEntry> entries;
+        public void Insert(int index, Metadata.Entry item)
+        {
+            CheckWriteable();
+            entries.Insert(index, item);
+        }
 
-        public Metadata(ImmutableList<MetadataEntry> entries)
+        public void RemoveAt(int index)
         {
-            this.entries = entries;
+            CheckWriteable();
+            entries.RemoveAt(index);
         }
 
-        public ImmutableList<MetadataEntry> Entries
+        public Metadata.Entry this[int index]
         {
             get
             {
-                return this.entries;
+                return entries[index];
+            }
+
+            set
+            {
+                CheckWriteable();
+                entries[index] = value;
             }
         }
 
-        public static Builder CreateBuilder()
+        public void Add(Metadata.Entry item)
+        {
+            CheckWriteable();
+            entries.Add(item);
+        }
+
+        public void Clear()
+        {
+            CheckWriteable();
+            entries.Clear();
+        }
+
+        public bool Contains(Metadata.Entry item)
+        {
+            return entries.Contains(item);
+        }
+
+        public void CopyTo(Metadata.Entry[] array, int arrayIndex)
         {
-            return new Builder();
+            entries.CopyTo(array, arrayIndex);
         }
-       
-        public struct MetadataEntry
+
+        public int Count
+        {
+            get { return entries.Count; }
+        }
+
+        public bool IsReadOnly
+        {
+            get { return readOnly; }
+        }
+
+        public bool Remove(Metadata.Entry item)
+        {
+            CheckWriteable();
+            return entries.Remove(item);
+        }
+
+        public IEnumerator<Metadata.Entry> GetEnumerator()
+        {
+            return entries.GetEnumerator();
+        }
+
+        IEnumerator System.Collections.IEnumerable.GetEnumerator()
+        {
+            return entries.GetEnumerator();
+        }
+
+        private void CheckWriteable()
+        {
+            Preconditions.CheckState(!readOnly, "Object is read only");
+        }
+
+        #endregion
+
+        /// <summary>
+        /// Metadata entry
+        /// </summary>
+        public struct Entry
         {
+            private static readonly Encoding Encoding = Encoding.ASCII;
+
             readonly string key;
-            readonly byte[] valueBytes;
+            string value;
+            byte[] valueBytes;
 
-            public MetadataEntry(string key, byte[] valueBytes)
+            public Entry(string key, byte[] valueBytes)
             {
-                this.key = key;
-                this.valueBytes = valueBytes;
+                this.key = Preconditions.CheckNotNull(key);
+                this.value = null;
+                this.valueBytes = Preconditions.CheckNotNull(valueBytes);
             }
 
-            public MetadataEntry(string key, string value)
+            public Entry(string key, string value)
             {
-                this.key = key;
-                this.valueBytes = Encoding.ASCII.GetBytes(value);
+                this.key = Preconditions.CheckNotNull(key);
+                this.value = Preconditions.CheckNotNull(value);
+                this.valueBytes = null;
             }
 
             public string Key
@@ -89,38 +197,29 @@ namespace Grpc.Core
                 }
             }
 
-            // TODO: using ByteString would guarantee immutability.
             public byte[] ValueBytes
             {
                 get
                 {
-                    return this.valueBytes;
+                    if (valueBytes == null)
+                    {
+                        valueBytes = Encoding.GetBytes(value);
+                    }
+                    return valueBytes;
                 }
             }
-        }
 
-        public class Builder
-        {
-            readonly List<Metadata.MetadataEntry> entries = new List<Metadata.MetadataEntry>();
-
-            public List<MetadataEntry> Entries
+            public string Value
             {
                 get
                 {
-                    return entries;
+                    if (value == null)
+                    {
+                        value = Encoding.GetString(valueBytes);
+                    }
+                    return value;
                 }
             }
-
-            public Builder Add(MetadataEntry entry)
-            {
-                entries.Add(entry);
-                return this;
-            }
-
-            public Metadata Build()
-            {
-                return new Metadata(entries.ToImmutableList());
-            }
         }
     }
 }
diff --git a/src/csharp/Grpc.Core/Stub/StubConfiguration.cs b/src/csharp/Grpc.Core/Stub/StubConfiguration.cs
deleted file mode 100644
index 5bcb5b40d2da6a1397ba869524ff1a65229ac329..0000000000000000000000000000000000000000
--- a/src/csharp/Grpc.Core/Stub/StubConfiguration.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-#region Copyright notice and license
-
-// Copyright 2015, 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 Grpc.Core.Internal;
-using Grpc.Core.Utils;
-
-namespace Grpc.Core
-{
-    public delegate void HeaderInterceptorDelegate(Metadata.Builder headerBuilder);
-
-    public class StubConfiguration
-    {
-        /// <summary>
-        /// The default stub configuration.
-        /// </summary>
-        public static readonly StubConfiguration Default = new StubConfiguration((headerBuilder) => { });
-
-        readonly HeaderInterceptorDelegate headerInterceptor;
-
-        public StubConfiguration(HeaderInterceptorDelegate headerInterceptor)
-        {
-            this.headerInterceptor = Preconditions.CheckNotNull(headerInterceptor);
-        }
-
-        public HeaderInterceptorDelegate HeaderInterceptor
-        {
-            get
-            {
-                return headerInterceptor;
-            }
-        }
-    }
-}
diff --git a/src/csharp/Grpc.Core/Version.cs b/src/csharp/Grpc.Core/Version.cs
index 972f495bd7dc06590f6dff550067b2fbb66e2c94..f1db1f61578e1fdfc45c6000ffbc6916240c22dc 100644
--- a/src/csharp/Grpc.Core/Version.cs
+++ b/src/csharp/Grpc.Core/Version.cs
@@ -3,4 +3,3 @@ using System.Runtime.CompilerServices;
 
 // The current version of gRPC C#.
 [assembly: AssemblyVersion("0.6.0.*")]
-
diff --git a/src/csharp/Grpc.Examples.MathClient/MathClient.cs b/src/csharp/Grpc.Examples.MathClient/MathClient.cs
index b7637214600a7f1a28e37c78345a5bcd0dbd10f4..cfe2a0691607c27ed2608b91d9fa097a6c59a0c6 100644
--- a/src/csharp/Grpc.Examples.MathClient/MathClient.cs
+++ b/src/csharp/Grpc.Examples.MathClient/MathClient.cs
@@ -41,18 +41,18 @@ namespace math
         {
             using (Channel channel = new Channel("127.0.0.1", 23456))
             {
-                Math.IMathClient stub = new Math.MathClient(channel);
-                MathExamples.DivExample(stub);
+                Math.IMathClient client = new Math.MathClient(channel);
+                MathExamples.DivExample(client);
 
-                MathExamples.DivAsyncExample(stub).Wait();
+                MathExamples.DivAsyncExample(client).Wait();
 
-                MathExamples.FibExample(stub).Wait();
+                MathExamples.FibExample(client).Wait();
 
-                MathExamples.SumExample(stub).Wait();
+                MathExamples.SumExample(client).Wait();
 
-                MathExamples.DivManyExample(stub).Wait();
+                MathExamples.DivManyExample(client).Wait();
 
-                MathExamples.DependendRequestsExample(stub).Wait();
+                MathExamples.DependendRequestsExample(client).Wait();
             }
 
             GrpcEnvironment.Shutdown();
diff --git a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
index 10dceb60aaad70fca4004551e220870dcc0d0636..e7c4b331208de2f09590830889e3423ae123455f 100644
--- a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
+++ b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
@@ -49,7 +49,7 @@ namespace math.Tests
         string host = "localhost";
         Server server;
         Channel channel;
-        Math.IMathClient client;
+        Math.MathClient client;
 
         [TestFixtureSetUp]
         public void Init()
@@ -59,14 +59,14 @@ namespace math.Tests
             int port = server.AddListeningPort(host, Server.PickUnusedPort);
             server.Start();
             channel = new Channel(host, port);
+            client = Math.NewClient(channel);
 
             // TODO(jtattermusch): get rid of the custom header here once we have dedicated tests
             // for header support.
-            var stubConfig = new StubConfiguration((headerBuilder) =>
+            client.HeaderInterceptor = (metadata) =>
             {
-                headerBuilder.Add(new Metadata.MetadataEntry("customHeader", "abcdef"));
-            });
-            client = Math.NewStub(channel, stubConfig);
+                metadata.Add(new Metadata.Entry("customHeader", "abcdef"));
+            };
         }
 
         [TestFixtureTearDown]
diff --git a/src/csharp/Grpc.Examples/MathExamples.cs b/src/csharp/Grpc.Examples/MathExamples.cs
index d2cfbee18f7fad6c4d69c1a16cd2bf08b2955082..7deb6516893b9957433225e30eca249780036d19 100644
--- a/src/csharp/Grpc.Examples/MathExamples.cs
+++ b/src/csharp/Grpc.Examples/MathExamples.cs
@@ -38,29 +38,29 @@ namespace math
 {
     public static class MathExamples
     {
-        public static void DivExample(Math.IMathClient stub)
+        public static void DivExample(Math.IMathClient client)
         {
-            DivReply result = stub.Div(new DivArgs.Builder { Dividend = 10, Divisor = 3 }.Build());
+            DivReply result = client.Div(new DivArgs.Builder { Dividend = 10, Divisor = 3 }.Build());
             Console.WriteLine("Div Result: " + result);
         }
 
-        public static async Task DivAsyncExample(Math.IMathClient stub)
+        public static async Task DivAsyncExample(Math.IMathClient client)
         {
-            Task<DivReply> resultTask = stub.DivAsync(new DivArgs.Builder { Dividend = 4, Divisor = 5 }.Build());
+            Task<DivReply> resultTask = client.DivAsync(new DivArgs.Builder { Dividend = 4, Divisor = 5 }.Build());
             DivReply result = await resultTask;
             Console.WriteLine("DivAsync Result: " + result);
         }
 
-        public static async Task FibExample(Math.IMathClient stub)
+        public static async Task FibExample(Math.IMathClient client)
         {
-            using (var call = stub.Fib(new FibArgs.Builder { Limit = 5 }.Build()))
+            using (var call = client.Fib(new FibArgs.Builder { Limit = 5 }.Build()))
             {
                 List<Num> result = await call.ResponseStream.ToList();
                 Console.WriteLine("Fib Result: " + string.Join("|", result));
             }
         }
 
-        public static async Task SumExample(Math.IMathClient stub)
+        public static async Task SumExample(Math.IMathClient client)
         {
             var numbers = new List<Num>
             {
@@ -69,14 +69,14 @@ namespace math
                 new Num.Builder { Num_ = 3 }.Build()
             };
 
-            using (var call = stub.Sum())
+            using (var call = client.Sum())
             {
                 await call.RequestStream.WriteAll(numbers);
                 Console.WriteLine("Sum Result: " + await call.Result);
             }
         }
 
-        public static async Task DivManyExample(Math.IMathClient stub)
+        public static async Task DivManyExample(Math.IMathClient client)
         {
             var divArgsList = new List<DivArgs>
             {
@@ -84,14 +84,14 @@ namespace math
                 new DivArgs.Builder { Dividend = 100, Divisor = 21 }.Build(),
                 new DivArgs.Builder { Dividend = 7, Divisor = 2 }.Build()
             };
-            using (var call = stub.DivMany())
+            using (var call = client.DivMany())
             { 
                 await call.RequestStream.WriteAll(divArgsList);
                 Console.WriteLine("DivMany Result: " + string.Join("|", await call.ResponseStream.ToList()));
             }
         }
 
-        public static async Task DependendRequestsExample(Math.IMathClient stub)
+        public static async Task DependendRequestsExample(Math.IMathClient client)
         {
             var numbers = new List<Num>
             {
@@ -101,13 +101,13 @@ namespace math
             };
 
             Num sum;
-            using (var sumCall = stub.Sum())
+            using (var sumCall = client.Sum())
             {
                 await sumCall.RequestStream.WriteAll(numbers);
                 sum = await sumCall.Result;
             }
 
-            DivReply result = await stub.DivAsync(new DivArgs.Builder { Dividend = sum.Num_, Divisor = numbers.Count }.Build());
+            DivReply result = await client.DivAsync(new DivArgs.Builder { Dividend = sum.Num_, Divisor = numbers.Count }.Build());
             Console.WriteLine("Avg Result: " + result);
         }
     }
diff --git a/src/csharp/Grpc.Examples/MathGrpc.cs b/src/csharp/Grpc.Examples/MathGrpc.cs
index b9efc44e8c1e4bdd6a704208bd3f827032fa4bd8..1805972ce33f2dacdee3132133385237f6bf9649 100644
--- a/src/csharp/Grpc.Examples/MathGrpc.cs
+++ b/src/csharp/Grpc.Examples/MathGrpc.cs
@@ -41,14 +41,14 @@ namespace math {
         __Marshaller_Num,
         __Marshaller_Num);
 
-    // client-side stub interface
+    // client interface
     public interface IMathClient
     {
-      global::math.DivReply Div(global::math.DivArgs request, CancellationToken token = default(CancellationToken));
-      Task<global::math.DivReply> DivAsync(global::math.DivArgs request, CancellationToken token = default(CancellationToken));
-      AsyncDuplexStreamingCall<global::math.DivArgs, global::math.DivReply> DivMany(CancellationToken token = default(CancellationToken));
-      AsyncServerStreamingCall<global::math.Num> Fib(global::math.FibArgs request, CancellationToken token = default(CancellationToken));
-      AsyncClientStreamingCall<global::math.Num, global::math.Num> Sum(CancellationToken token = default(CancellationToken));
+      global::math.DivReply Div(global::math.DivArgs request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
+      Task<global::math.DivReply> DivAsync(global::math.DivArgs request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
+      AsyncDuplexStreamingCall<global::math.DivArgs, global::math.DivReply> DivMany(Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
+      AsyncServerStreamingCall<global::math.Num> Fib(global::math.FibArgs request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
+      AsyncClientStreamingCall<global::math.Num, global::math.Num> Sum(Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
     }
 
     // server-side interface
@@ -61,38 +61,35 @@ namespace math {
     }
 
     // client stub
-    public class MathClient : AbstractStub<MathClient, StubConfiguration>, IMathClient
+    public class MathClient : ClientBase, IMathClient
     {
-      public MathClient(Channel channel) : this(channel, StubConfiguration.Default)
+      public MathClient(Channel channel) : base(channel)
       {
       }
-      public MathClient(Channel channel, StubConfiguration config) : base(channel, config)
+      public global::math.DivReply Div(global::math.DivArgs request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
+        var call = CreateCall(__ServiceName, __Method_Div, headers);
+        return Calls.BlockingUnaryCall(call, request, cancellationToken);
       }
-      public global::math.DivReply Div(global::math.DivArgs request, CancellationToken token = default(CancellationToken))
+      public Task<global::math.DivReply> DivAsync(global::math.DivArgs request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__ServiceName, __Method_Div);
-        return Calls.BlockingUnaryCall(call, request, token);
+        var call = CreateCall(__ServiceName, __Method_Div, headers);
+        return Calls.AsyncUnaryCall(call, request, cancellationToken);
       }
-      public Task<global::math.DivReply> DivAsync(global::math.DivArgs request, CancellationToken token = default(CancellationToken))
+      public AsyncDuplexStreamingCall<global::math.DivArgs, global::math.DivReply> DivMany(Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__ServiceName, __Method_Div);
-        return Calls.AsyncUnaryCall(call, request, token);
+        var call = CreateCall(__ServiceName, __Method_DivMany, headers);
+        return Calls.AsyncDuplexStreamingCall(call, cancellationToken);
       }
-      public AsyncDuplexStreamingCall<global::math.DivArgs, global::math.DivReply> DivMany(CancellationToken token = default(CancellationToken))
+      public AsyncServerStreamingCall<global::math.Num> Fib(global::math.FibArgs request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__ServiceName, __Method_DivMany);
-        return Calls.AsyncDuplexStreamingCall(call, token);
+        var call = CreateCall(__ServiceName, __Method_Fib, headers);
+        return Calls.AsyncServerStreamingCall(call, request, cancellationToken);
       }
-      public AsyncServerStreamingCall<global::math.Num> Fib(global::math.FibArgs request, CancellationToken token = default(CancellationToken))
+      public AsyncClientStreamingCall<global::math.Num, global::math.Num> Sum(Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__ServiceName, __Method_Fib);
-        return Calls.AsyncServerStreamingCall(call, request, token);
-      }
-      public AsyncClientStreamingCall<global::math.Num, global::math.Num> Sum(CancellationToken token = default(CancellationToken))
-      {
-        var call = CreateCall(__ServiceName, __Method_Sum);
-        return Calls.AsyncClientStreamingCall(call, token);
+        var call = CreateCall(__ServiceName, __Method_Sum, headers);
+        return Calls.AsyncClientStreamingCall(call, cancellationToken);
       }
     }
 
@@ -106,17 +103,12 @@ namespace math {
           .AddMethod(__Method_Sum, serviceImpl.Sum).Build();
     }
 
-    // creates a new client stub
-    public static IMathClient NewStub(Channel channel)
+    // creates a new client
+    public static MathClient NewClient(Channel channel)
     {
       return new MathClient(channel);
     }
 
-    // creates a new client stub
-    public static IMathClient NewStub(Channel channel, StubConfiguration config)
-    {
-      return new MathClient(channel, config);
-    }
   }
 }
 #endregion
diff --git a/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
index 0ac1add8e4289c08717e84b3fde662065de62f6c..73ff0e74b57daeeb87b9fd4563b6efaf2bf71281 100644
--- a/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
+++ b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
@@ -63,7 +63,7 @@ namespace Grpc.HealthCheck.Tests
             server.Start();
             channel = new Channel(Host, port);
 
-            client = Grpc.Health.V1Alpha.Health.NewStub(channel);
+            client = Grpc.Health.V1Alpha.Health.NewClient(channel);
         }
 
         [TestFixtureTearDown]
diff --git a/src/csharp/Grpc.HealthCheck/HealthGrpc.cs b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs
index ed9fc4ed77493e0592677293af83d2449fcc6cc9..3aebdcb557309a1169beffcc4383a9c2ffc0f970 100644
--- a/src/csharp/Grpc.HealthCheck/HealthGrpc.cs
+++ b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs
@@ -21,11 +21,11 @@ namespace Grpc.Health.V1Alpha {
         __Marshaller_HealthCheckRequest,
         __Marshaller_HealthCheckResponse);
 
-    // client-side stub interface
+    // client interface
     public interface IHealthClient
     {
-      global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CancellationToken token = default(CancellationToken));
-      Task<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CancellationToken token = default(CancellationToken));
+      global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
+      Task<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
     }
 
     // server-side interface
@@ -35,23 +35,20 @@ namespace Grpc.Health.V1Alpha {
     }
 
     // client stub
-    public class HealthClient : AbstractStub<HealthClient, StubConfiguration>, IHealthClient
+    public class HealthClient : ClientBase, IHealthClient
     {
-      public HealthClient(Channel channel) : this(channel, StubConfiguration.Default)
+      public HealthClient(Channel channel) : base(channel)
       {
       }
-      public HealthClient(Channel channel, StubConfiguration config) : base(channel, config)
+      public global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
+        var call = CreateCall(__ServiceName, __Method_Check, headers);
+        return Calls.BlockingUnaryCall(call, request, cancellationToken);
       }
-      public global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CancellationToken token = default(CancellationToken))
+      public Task<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__ServiceName, __Method_Check);
-        return Calls.BlockingUnaryCall(call, request, token);
-      }
-      public Task<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CancellationToken token = default(CancellationToken))
-      {
-        var call = CreateCall(__ServiceName, __Method_Check);
-        return Calls.AsyncUnaryCall(call, request, token);
+        var call = CreateCall(__ServiceName, __Method_Check, headers);
+        return Calls.AsyncUnaryCall(call, request, cancellationToken);
       }
     }
 
@@ -62,17 +59,12 @@ namespace Grpc.Health.V1Alpha {
           .AddMethod(__Method_Check, serviceImpl.Check).Build();
     }
 
-    // creates a new client stub
-    public static IHealthClient NewStub(Channel channel)
+    // creates a new client
+    public static HealthClient NewClient(Channel channel)
     {
       return new HealthClient(channel);
     }
 
-    // creates a new client stub
-    public static IHealthClient NewStub(Channel channel, StubConfiguration config)
-    {
-      return new HealthClient(channel, config);
-    }
   }
 }
 #endregion
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
index bdcb2c505c251d3b3a30fea1ca6974b7463ce6c1..05e732dbd462b914f90f0228f11e8bf3e46b5880 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
@@ -119,7 +119,7 @@ namespace Grpc.IntegrationTesting
 
             using (Channel channel = new Channel(options.serverHost, options.serverPort.Value, credentials, channelOptions))
             {
-                var stubConfig = StubConfiguration.Default;
+                TestService.TestServiceClient client = new TestService.TestServiceClient(channel);
                 if (options.testCase == "service_account_creds" || options.testCase == "compute_engine_creds")
                 {
                     var credential = GoogleCredential.GetApplicationDefault();
@@ -127,10 +127,9 @@ namespace Grpc.IntegrationTesting
                     {
                         credential = credential.CreateScoped(new[] { AuthScope });
                     }
-                    stubConfig = new StubConfiguration(OAuth2InterceptorFactory.Create(credential));
+                    client.HeaderInterceptor = OAuth2InterceptorFactory.Create(credential);
                 }
 
-                TestService.ITestServiceClient client = new TestService.TestServiceClient(channel, stubConfig);
                 RunTestCase(options.testCase, client);
             }
             GrpcEnvironment.Shutdown();
@@ -363,7 +362,7 @@ namespace Grpc.IntegrationTesting
                 Console.WriteLine("running cancel_after_begin");
 
                 var cts = new CancellationTokenSource();
-                using (var call = client.StreamingInputCall(cts.Token))
+                using (var call = client.StreamingInputCall(cancellationToken: cts.Token))
                 {
                     // TODO(jtattermusch): we need this to ensure call has been initiated once we cancel it.
                     await Task.Delay(1000);
@@ -390,7 +389,7 @@ namespace Grpc.IntegrationTesting
                 Console.WriteLine("running cancel_after_first_response");
 
                 var cts = new CancellationTokenSource();
-                using (var call = client.FullDuplexCall(cts.Token))
+                using (var call = client.FullDuplexCall(cancellationToken: cts.Token))
                 {
                     await call.RequestStream.WriteAsync(StreamingOutputCallRequest.CreateBuilder()
                         .SetResponseType(PayloadType.COMPRESSABLE)
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
index 6c2da9d2ee672320e7c2017affa1c76cd0ba2039..f306289cfb5dc9031c2bfb4fdeb5a303b3ef91de 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
@@ -65,7 +65,7 @@ namespace Grpc.IntegrationTesting
                 new ChannelOption(ChannelOptions.SslTargetNameOverride, TestCredentials.DefaultHostOverride)
             };
             channel = new Channel(host, port, TestCredentials.CreateTestClientCredentials(true), options);
-            client = TestService.NewStub(channel);
+            client = TestService.NewClient(channel);
         }
 
         [TestFixtureTearDown]
diff --git a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
index ee077f9f56f146bc9dc0aa06cbaa8222133a0961..96d9b2371770eec0cf3956e6dfa17e5e0665f98c 100644
--- a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
+++ b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
@@ -56,17 +56,17 @@ namespace grpc.testing {
         __Marshaller_StreamingOutputCallRequest,
         __Marshaller_StreamingOutputCallResponse);
 
-    // client-side stub interface
+    // client interface
     public interface ITestServiceClient
     {
-      global::grpc.testing.Empty EmptyCall(global::grpc.testing.Empty request, CancellationToken token = default(CancellationToken));
-      Task<global::grpc.testing.Empty> EmptyCallAsync(global::grpc.testing.Empty request, CancellationToken token = default(CancellationToken));
-      global::grpc.testing.SimpleResponse UnaryCall(global::grpc.testing.SimpleRequest request, CancellationToken token = default(CancellationToken));
-      Task<global::grpc.testing.SimpleResponse> UnaryCallAsync(global::grpc.testing.SimpleRequest request, CancellationToken token = default(CancellationToken));
-      AsyncServerStreamingCall<global::grpc.testing.StreamingOutputCallResponse> StreamingOutputCall(global::grpc.testing.StreamingOutputCallRequest request, CancellationToken token = default(CancellationToken));
-      AsyncClientStreamingCall<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> StreamingInputCall(CancellationToken token = default(CancellationToken));
-      AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> FullDuplexCall(CancellationToken token = default(CancellationToken));
-      AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> HalfDuplexCall(CancellationToken token = default(CancellationToken));
+      global::grpc.testing.Empty EmptyCall(global::grpc.testing.Empty request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
+      Task<global::grpc.testing.Empty> EmptyCallAsync(global::grpc.testing.Empty request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
+      global::grpc.testing.SimpleResponse UnaryCall(global::grpc.testing.SimpleRequest request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
+      Task<global::grpc.testing.SimpleResponse> UnaryCallAsync(global::grpc.testing.SimpleRequest request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
+      AsyncServerStreamingCall<global::grpc.testing.StreamingOutputCallResponse> StreamingOutputCall(global::grpc.testing.StreamingOutputCallRequest request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
+      AsyncClientStreamingCall<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> StreamingInputCall(Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
+      AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> FullDuplexCall(Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
+      AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> HalfDuplexCall(Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken));
     }
 
     // server-side interface
@@ -81,53 +81,50 @@ namespace grpc.testing {
     }
 
     // client stub
-    public class TestServiceClient : AbstractStub<TestServiceClient, StubConfiguration>, ITestServiceClient
+    public class TestServiceClient : ClientBase, ITestServiceClient
     {
-      public TestServiceClient(Channel channel) : this(channel, StubConfiguration.Default)
+      public TestServiceClient(Channel channel) : base(channel)
       {
       }
-      public TestServiceClient(Channel channel, StubConfiguration config) : base(channel, config)
+      public global::grpc.testing.Empty EmptyCall(global::grpc.testing.Empty request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
+        var call = CreateCall(__ServiceName, __Method_EmptyCall, headers);
+        return Calls.BlockingUnaryCall(call, request, cancellationToken);
       }
-      public global::grpc.testing.Empty EmptyCall(global::grpc.testing.Empty request, CancellationToken token = default(CancellationToken))
+      public Task<global::grpc.testing.Empty> EmptyCallAsync(global::grpc.testing.Empty request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__ServiceName, __Method_EmptyCall);
-        return Calls.BlockingUnaryCall(call, request, token);
+        var call = CreateCall(__ServiceName, __Method_EmptyCall, headers);
+        return Calls.AsyncUnaryCall(call, request, cancellationToken);
       }
-      public Task<global::grpc.testing.Empty> EmptyCallAsync(global::grpc.testing.Empty request, CancellationToken token = default(CancellationToken))
+      public global::grpc.testing.SimpleResponse UnaryCall(global::grpc.testing.SimpleRequest request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__ServiceName, __Method_EmptyCall);
-        return Calls.AsyncUnaryCall(call, request, token);
+        var call = CreateCall(__ServiceName, __Method_UnaryCall, headers);
+        return Calls.BlockingUnaryCall(call, request, cancellationToken);
       }
-      public global::grpc.testing.SimpleResponse UnaryCall(global::grpc.testing.SimpleRequest request, CancellationToken token = default(CancellationToken))
+      public Task<global::grpc.testing.SimpleResponse> UnaryCallAsync(global::grpc.testing.SimpleRequest request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__ServiceName, __Method_UnaryCall);
-        return Calls.BlockingUnaryCall(call, request, token);
+        var call = CreateCall(__ServiceName, __Method_UnaryCall, headers);
+        return Calls.AsyncUnaryCall(call, request, cancellationToken);
       }
-      public Task<global::grpc.testing.SimpleResponse> UnaryCallAsync(global::grpc.testing.SimpleRequest request, CancellationToken token = default(CancellationToken))
+      public AsyncServerStreamingCall<global::grpc.testing.StreamingOutputCallResponse> StreamingOutputCall(global::grpc.testing.StreamingOutputCallRequest request, Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__ServiceName, __Method_UnaryCall);
-        return Calls.AsyncUnaryCall(call, request, token);
+        var call = CreateCall(__ServiceName, __Method_StreamingOutputCall, headers);
+        return Calls.AsyncServerStreamingCall(call, request, cancellationToken);
       }
-      public AsyncServerStreamingCall<global::grpc.testing.StreamingOutputCallResponse> StreamingOutputCall(global::grpc.testing.StreamingOutputCallRequest request, CancellationToken token = default(CancellationToken))
+      public AsyncClientStreamingCall<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> StreamingInputCall(Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__ServiceName, __Method_StreamingOutputCall);
-        return Calls.AsyncServerStreamingCall(call, request, token);
+        var call = CreateCall(__ServiceName, __Method_StreamingInputCall, headers);
+        return Calls.AsyncClientStreamingCall(call, cancellationToken);
       }
-      public AsyncClientStreamingCall<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> StreamingInputCall(CancellationToken token = default(CancellationToken))
+      public AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> FullDuplexCall(Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__ServiceName, __Method_StreamingInputCall);
-        return Calls.AsyncClientStreamingCall(call, token);
+        var call = CreateCall(__ServiceName, __Method_FullDuplexCall, headers);
+        return Calls.AsyncDuplexStreamingCall(call, cancellationToken);
       }
-      public AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> FullDuplexCall(CancellationToken token = default(CancellationToken))
+      public AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> HalfDuplexCall(Metadata headers = null, CancellationToken cancellationToken = default(CancellationToken))
       {
-        var call = CreateCall(__ServiceName, __Method_FullDuplexCall);
-        return Calls.AsyncDuplexStreamingCall(call, token);
-      }
-      public AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> HalfDuplexCall(CancellationToken token = default(CancellationToken))
-      {
-        var call = CreateCall(__ServiceName, __Method_HalfDuplexCall);
-        return Calls.AsyncDuplexStreamingCall(call, token);
+        var call = CreateCall(__ServiceName, __Method_HalfDuplexCall, headers);
+        return Calls.AsyncDuplexStreamingCall(call, cancellationToken);
       }
     }
 
@@ -143,17 +140,12 @@ namespace grpc.testing {
           .AddMethod(__Method_HalfDuplexCall, serviceImpl.HalfDuplexCall).Build();
     }
 
-    // creates a new client stub
-    public static ITestServiceClient NewStub(Channel channel)
+    // creates a new client
+    public static TestServiceClient NewClient(Channel channel)
     {
       return new TestServiceClient(channel);
     }
 
-    // creates a new client stub
-    public static ITestServiceClient NewStub(Channel channel, StubConfiguration config)
-    {
-      return new TestServiceClient(channel, config);
-    }
   }
 }
 #endregion
diff --git a/src/csharp/generate_proto_csharp.sh b/src/csharp/generate_proto_csharp.sh
index 6eb3887ea109f12513d2ad967b83a8611cae00c3..7c3ba7092207687b3c8f76ef05fff5c170912e8b 100755
--- a/src/csharp/generate_proto_csharp.sh
+++ b/src/csharp/generate_proto_csharp.sh
@@ -32,16 +32,17 @@
 set +e
 cd $(dirname $0)
 
+PROTOC=../../bins/opt/protobuf/protoc
 PLUGIN=protoc-gen-grpc=../../bins/opt/grpc_csharp_plugin
 EXAMPLES_DIR=Grpc.Examples
 INTEROP_DIR=Grpc.IntegrationTesting
 HEALTHCHECK_DIR=Grpc.HealthCheck
 
-protoc --plugin=$PLUGIN --grpc_out=$EXAMPLES_DIR \
+$PROTOC --plugin=$PLUGIN --grpc_out=$EXAMPLES_DIR \
     -I $EXAMPLES_DIR/proto $EXAMPLES_DIR/proto/math.proto
 
-protoc --plugin=$PLUGIN --grpc_out=$INTEROP_DIR \
+$PROTOC --plugin=$PLUGIN --grpc_out=$INTEROP_DIR \
     -I $INTEROP_DIR/proto $INTEROP_DIR/proto/test.proto
-	
-protoc --plugin=$PLUGIN --grpc_out=$HEALTHCHECK_DIR \
+
+$PROTOC --plugin=$PLUGIN --grpc_out=$HEALTHCHECK_DIR \
     -I $HEALTHCHECK_DIR/proto $HEALTHCHECK_DIR/proto/health.proto