Getting .NET Core and SharePoint CSOM Play Nice

Recently, I had to migrate some existing code that targeted .NET Framework 4.6.1 to .NET Standard 2 and .NET Core 2. The code used Client-Side Object Model (CSOM) to access SharePoint server object model. The migration did not go as smooth as I thought it would. In this post, I wish to discuss how I got the issues resolved. The source code has been simplified so that we can focus on the errors.

I have a .NET class library project SharePoint.NetFramework that uses client-side object model (CSOM) to access SharePoint server object model. The target framework for the project is .NET Framework 4.6.1. The project has a reference to the latest version of Microsoft.SharePointOnline.CSOM NuGet package.

Microsoft.SharePointOnline.CSOM


using System;
using System.Linq;
using Microsoft.SharePoint.Client;

namespace SharePoint.NetFramework {
   public class Repository {
      public void PrintFoldersAndFiles(string siteUrl, string listName) {
         using (var context = new ClientContext(siteUrl)) {
            context.AuthenticationMode = ClientAuthenticationMode.Anonymous;

            var folder = context.Web.GetFolderByServerRelativeUrl(listName);
            var subFolders = folder.Folders;
            var files = folder.Files;

            context.Load(folder);
            context.Load(subFolders);
            context.Load(files);

            if (context.HasPendingRequest) {
               context.ExecuteQuery();
            }

            subFolders.ToList().ForEach(x => Console.WriteLine($"-- {x.Name}"));
            files.ToList().ForEach(x => Console.WriteLine($"-- {x.Name}"));
         }
      }
   }
}

There is also a console application project Client.NetFramework that targets .NET Framework 4.6.1. This project has a reference to the SharePoint.NetFramework project.


using System;
using System.Configuration;

namespace Client.NetFramework {
   public class Program {
      public static void Main(string[] args) {
         Console.Title = "SharePoint CSOM with .NET Framework";
         Console.ForegroundColor = ConsoleColor.Green;

         try {
            var siteUrl = ConfigurationManager.AppSettings["siteUrl"];
            var listName = ConfigurationManager.AppSettings["listName"];

            var repository = new SharePoint.NetFramework.Repository();
            repository.PrintFoldersAndFiles(siteUrl, listName);
         }
         catch (Exception e) {
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.WriteLine(e);
         }

         Console.WriteLine();
         Console.WriteLine("Done, Press [ENTER] to Exit!");
         Console.ReadLine();
      }
   }
}

When we run the console application, we can see that everything works as expected. The code in the current form just displays the contents of a specific library within a SharePoint site.

.NET Framework Console Application

Now that we have confirmed that the code is working, let us begin to migrate the code. I started by creating a new library project SharePoint.NetStandard that targets .NET Standard 2.0. The code is exactly the same as the project SharePoint.NetFramework. When a reference to the Microsoft.SharePointOnline.CSOM NuGet package was added to the project, Visual Studio immediately started to complain.

Package Not Compatible

I chose to ignore the warning and proceeded to create a new console application Client.NetCore that targets .NET Core 2. I had to make a few changes to the existing application code as expected.


using System;
using Microsoft.Extensions.Configuration;

namespace Client.NetCore {
   class Program {
      static void Main(string[] args) {
         Console.Title = "SharePoint CSOM with .NET Core";
         Console.ForegroundColor = ConsoleColor.Green;

         try {
            var configurationRoot = new ConfigurationBuilder()
               .AddXmlFile("App.config")
               .Build();

            var section = configurationRoot.GetSection("mySettings");
            var siteUrl = section["siteUrl"];
            var listName = section["listName"];

            var repository = new SharePoint.NetFramework.Repository();
            repository.PrintFoldersAndFiles(siteUrl, listName);
         }
         catch (Exception e) {
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.WriteLine(e);
         }

         Console.WriteLine();
         Console.WriteLine("Done, Press [ENTER] to Exit!");
         Console.ReadLine();
      }
   }
}

Now that the code was migrated, I ran the new console application Client.NetCore and it resulted in an error. Below is the output:


System.Net.WebException: The remote server returned an error: (400) Bad Request.
   at System.Net.HttpWebRequest.GetResponse()
   at Microsoft.SharePoint.Client.SPWebRequestExecutor.Execute()
   at Microsoft.SharePoint.Client.ClientContext.GetFormDigestInfoPrivate()
   at Microsoft.SharePoint.Client.ClientContext.EnsureFormDigest()
   at Microsoft.SharePoint.Client.ClientContext.ExecuteQuery()
   at SharePoint.NetFramework.Repository.PrintFoldersAndFiles(String siteUrl, String listName) in ...\SharePoint Demo\SharePoint.NetStandard\Repository.cs:line 20
   at Client.NetCore.Program.Main(String[] args) in ...\SharePoint Demo\Client.NetCore\Program.cs:line 20

After digging through the debug output window, the following lines captured my attention:


'dotnet.exe' (CoreCLR: clrhost): Loaded '...\.nuget\packages\microsoft.sharepointonline.csom\16.1.7018.1200\lib\net45\Microsoft.SharePoint.Client.dll'.
'dotnet.exe' (CoreCLR: clrhost): Loaded '...\.nuget\packages\microsoft.sharepointonline.csom\16.1.7018.1200\lib\net45\Microsoft.SharePoint.Client.Runtime.dll'.
Exception thrown: 'System.Net.WebException' in Microsoft.SharePoint.Client.dll

The issue seemed to be that we might be using wrong assemblies with .NET Standard and .NET Core. I looked in the Microsoft.SharePointOnline.CSOM package folder on my local machine and noticed that there was a netcore45 folder. This folder contained portable assemblies.

Portable Assemblies

I decided to remove the reference to the Microsoft.SharePointOnline.CSOM NuGet package from the SharePoint.NetStandard project and manually add references to Microsoft.SharePoint.Client.Portable.dll and Microsoft.SharePoint.Client.Runtime.Portable.dll. When building the project, I got a new error:

Invalid Method Error

This was an easy fix. I made changes to the source code as below:


using System;
using System.Linq;
using Microsoft.SharePoint.Client;

namespace SharePoint.NetStandard {
   public class Repository {
      public void PrintFoldersAndFiles(string siteUrl, string listName) {
         using (var context = new ClientContext(siteUrl)) {
            context.AuthenticationMode = ClientAuthenticationMode.Anonymous;

            var folder = context.Web.GetFolderByServerRelativeUrl(listName);
            var subFolders = folder.Folders;
            var files = folder.Files;

            context.Load(folder);
            context.Load(subFolders);
            context.Load(files);

            if (context.HasPendingRequest) {
               context.ExecuteQueryAsync()
                  .Wait();
            }

            subFolders.ToList().ForEach(x => Console.WriteLine($"-- {x.Name}"));
            files.ToList().ForEach(x => Console.WriteLine($"-- {x.Name}"));
         }
      }
   }
}

Now, when I ran the console application Client.NetCore, I got a different error:


System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.SharePoint.Client.Portable, Version=16.1.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c'. The system cannot find the file specified.
File name: 'Microsoft.SharePoint.Client.Portable, Version=16.1.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c'
   at SharePoint.NetFramework.Repository.PrintFoldersAndFiles(String siteUrl, String listName)
   at Client.NetCore.Program.Main(String[] args) in D:\Development\Visual Studio 2017\Projects\SharePoint Demo\Client.NetCore\Program.cs:line 20

Looked like I needed to add the assembly references to the Client.NetCore project also. After adding the assembly reference, I ran the console application Client.NetCore again. And an error (again):


System.AggregateException: One or more errors occurred. (Cannot find platform service library. For Windows Store application, please include Microsoft.SharePoint.Client.Runtime.WindowsStore.dll in the application package. For Windows Phone application, please include Microsoft.SharePoint.Client.Runtime.WindowsPhone.dll in the application package. For Windows application, please install Microsoft.SharePoint.Client.Runtime.Windows.dll in the GAC (Global Assembly Cache) or make it available for the Windows application.) ---> System.InvalidOperationException: Cannot find platform service library. For Windows Store application, please include Microsoft.SharePoint.Client.Runtime.WindowsStore.dll in the application package. For Windows Phone application, please include Microsoft.SharePoint.Client.Runtime.WindowsPhone.dll in the application package. For Windows application, please install Microsoft.SharePoint.Client.Runtime.Windows.dll in the GAC (Global Assembly Cache) or make it available for the Windows application.
   at Microsoft.SharePoint.Client.PlatformService.CheckPlatformServiceLibrary()
   at Microsoft.SharePoint.Client.ScriptTypeMap.d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.SharePoint.Client.ScriptTypeMap.d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.SharePoint.Client.ClientRuntimeContext.d__8.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.SharePoint.Client.ClientContext.d__4.MoveNext()
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at SharePoint.NetFramework.Repository.PrintFoldersAndFiles(String siteUrl, String listName) in D:\Development\Visual Studio 2017\Projects\SharePoint Demo\SharePoint.NetStandard\Repository.cs:line 20
   at Client.NetCore.Program.Main(String[] args) in D:\Development\Visual Studio 2017\Projects\SharePoint Demo\Client.NetCore\Program.cs:line 20
---> (Inner Exception #0) System.InvalidOperationException: Cannot find platform service library. For Windows Store application, please include Microsoft.SharePoint.Client.Runtime.WindowsStore.dll in the application package. For Windows Phone application, please include Microsoft.SharePoint.Client.Runtime.WindowsPhone.dll in the application package. For Windows application, please install Microsoft.SharePoint.Client.Runtime.Windows.dll in the GAC (Global Assembly Cache) or make it available for the Windows application.
   at Microsoft.SharePoint.Client.PlatformService.CheckPlatformServiceLibrary()
   at Microsoft.SharePoint.Client.ScriptTypeMap.d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.SharePoint.Client.ScriptTypeMap.d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.SharePoint.Client.ClientRuntimeContext.d__8.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.SharePoint.Client.ClientContext.d__4.MoveNext()<---

This time, the error was pretty detailed and it even provided the solution. We needed to add a reference to the Microsoft.SharePoint.Client.Runtime.Windows.dll assembly in both SharePoint.NetStandard and Client.NetCore projects. The assembly can be found in the same NuGet package folder. After adding the reference and running again, (drum rolls...) it worked.

Console Application

I still have not figured out why the correct assembly references were not added to the project in the first place. But it works now and that's all that matters.

4 Comments

Leave a Reply