diff --git a/src/csharp/Grpc.Core/Internal/PlatformApis.cs b/src/csharp/Grpc.Core/Internal/PlatformApis.cs
index 15391ddc647ab7889129bf82030c3896a3ec14e8..406204e0a753938950ff3af318595cdcdba5e83a 100644
--- a/src/csharp/Grpc.Core/Internal/PlatformApis.cs
+++ b/src/csharp/Grpc.Core/Internal/PlatformApis.cs
@@ -50,6 +50,7 @@ namespace Grpc.Core.Internal
         static readonly bool isMacOSX;
         static readonly bool isWindows;
         static readonly bool isMono;
+        static readonly bool isNetCore;
 
         static PlatformApis()
         {
@@ -57,6 +58,7 @@ namespace Grpc.Core.Internal
             isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
             isMacOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
             isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+            isNetCore = RuntimeInformation.FrameworkDescription.StartsWith(".NET Core");
 #else
             var platform = Environment.OSVersion.Platform;
 
@@ -64,6 +66,7 @@ namespace Grpc.Core.Internal
             isMacOSX = (platform == PlatformID.Unix && GetUname() == "Darwin");
             isLinux = (platform == PlatformID.Unix && !isMacOSX);
             isWindows = (platform == PlatformID.Win32NT || platform == PlatformID.Win32S || platform == PlatformID.Win32Windows);
+            isNetCore = false;
 #endif
             isMono = Type.GetType("Mono.Runtime") != null;
         }
@@ -88,6 +91,14 @@ namespace Grpc.Core.Internal
             get { return isMono; }
         }
 
+        /// <summary>
+        /// true if running on .NET Core (CoreCLR), false otherwise.
+        /// </summary>
+        public static bool IsNetCore
+        {
+            get { return isNetCore; }
+        }
+
         public static bool Is64Bit
         {
             get { return IntPtr.Size == 8; }
diff --git a/src/csharp/Grpc.Core/Internal/UnmanagedLibrary.cs b/src/csharp/Grpc.Core/Internal/UnmanagedLibrary.cs
index dc629bd714fae331490f7b1bdeaf772d07c10826..31e14028494719085a11d12c37c7b6239d38be46 100644
--- a/src/csharp/Grpc.Core/Internal/UnmanagedLibrary.cs
+++ b/src/csharp/Grpc.Core/Internal/UnmanagedLibrary.cs
@@ -44,10 +44,9 @@ namespace Grpc.Core.Internal
 {
     /// <summary>
     /// Represents a dynamically loaded unmanaged library in a (partially) platform independent manner.
-    /// An important difference in library loading semantics is that on Windows, once we load a dynamic library using LoadLibrary,
-    /// that library becomes instantly available for <c>DllImport</c> P/Invoke calls referring to the same library name.
-    /// On Unix systems, dlopen has somewhat different semantics, so we need to use dlsym and <c>Marshal.GetDelegateForFunctionPointer</c>
-    /// to obtain delegates to native methods.
+    /// First, the native library is loaded using dlopen (on Unix systems) or using LoadLibrary (on Windows).
+    /// dlsym or GetProcAddress are then used to obtain symbol addresses. <c>Marshal.GetDelegateForFunctionPointer</c>
+    /// transforms the addresses into delegates to native methods.
     /// See http://stackoverflow.com/questions/13461989/p-invoke-to-dynamically-loaded-library-on-mono.
     /// </summary>
     internal class UnmanagedLibrary
@@ -114,6 +113,10 @@ namespace Grpc.Core.Internal
                 {
                     return Mono.dlsym(this.handle, symbolName);
                 }
+                if (PlatformApis.IsNetCore)
+                {
+                    return CoreCLR.dlsym(this.handle, symbolName);
+                }
                 return Linux.dlsym(this.handle, symbolName);
             }
             if (PlatformApis.IsMacOSX)
@@ -149,6 +152,10 @@ namespace Grpc.Core.Internal
                 {
                     return Mono.dlopen(libraryPath, RTLD_GLOBAL + RTLD_LAZY);
                 }
+                if (PlatformApis.IsNetCore)
+                {
+                    return CoreCLR.dlopen(libraryPath, RTLD_GLOBAL + RTLD_LAZY);
+                }
                 return Linux.dlopen(libraryPath, RTLD_GLOBAL + RTLD_LAZY);
             }
             if (PlatformApis.IsMacOSX)
@@ -215,5 +222,19 @@ namespace Grpc.Core.Internal
             [DllImport("__Internal")]
             internal static extern IntPtr dlsym(IntPtr handle, string symbol);
         }
+
+        /// <summary>
+        /// Similarly as for Mono on Linux, we load symbols for
+        /// dlopen and dlsym from the "libcoreclr.so",
+        /// to avoid the dependency on libc-dev Linux.
+        /// </summary>
+        private static class CoreCLR
+        {
+            [DllImport("libcoreclr.so")]
+            internal static extern IntPtr dlopen(string filename, int flags);
+
+            [DllImport("libcoreclr.so")]
+            internal static extern IntPtr dlsym(IntPtr handle, string symbol);
+        }
     }
 }
diff --git a/tools/dockerfile/distribtest/csharp_ubuntu1404_x64/Dockerfile b/tools/dockerfile/distribtest/csharp_ubuntu1404_x64/Dockerfile
index 1f9a42e13f95f2d47e5fd94849981d1dd5b0a32c..ed6d87b228c0337744cef6d7debb68201e0d3f60 100644
--- a/tools/dockerfile/distribtest/csharp_ubuntu1404_x64/Dockerfile
+++ b/tools/dockerfile/distribtest/csharp_ubuntu1404_x64/Dockerfile
@@ -54,7 +54,3 @@ RUN mkdir warmup \
     && dotnet new \
     && cd .. \
     && rm -rf warmup
-
-# TODO(jtattermusch): without libc-dev, netcoreapp1.0 targets fail with
-# System.DllNotFoundException: Unable to load DLL 'libdl.so'
-RUN apt-get install -y libc-dev
\ No newline at end of file