Managing ClientContext.ExecuteQuery Errors in SharePoint CSOM

One of the underlying realities of working with SharePoint’s client side object model (CSOM) is that you are not continuously connected to SharePoint. This means that as you build commands and queries in CSOM that they are stored up and then executed in batch when you call the ClientContext.ExecuteQuery method. Another unfortunate reality of working with CSOM is that if anything in that batch of commands fails, then everything in that batch of commands fails – at least on the client. SharePoint is not a transactional system, so I’m not 100% sure how it handles that processing on the server side.

Now, I’m not really upset that things fail in batch. In theory, as long as you code everything right, there should be no failures. But this is SharePoint and that theory goes right out the window because there are some operations in SharePoint that are just built around errors. For example, to check if a folder exists you have to get the folder by URL and if that folder doesn’t exist SharePoint throws back an error. The problem with this is that you can’t run that code in batch or all the other code in the batch fails too. It would be nice if the folder just came back null instead of everything exploding.

For example, let’s take the following code:

var folderUrl = "/sites/dev/Lists/DemoList/FolderThatDoesNotExist/";
var contextUrl = https://SharePointServer/sites/YourSite/;

using (ClientContext context = new ClientContext(contextUrl))
{
  var folder = context.Web.GetFolderByServerRelativeUrl(folderUrl);
  context.Load(context.Web, w => w.Title);
  context.ExecuteQuery();
  Console.WriteLine(context.Web.Title);
  Console.WriteLine(folder.Name);
}

This is fairly straightforward. You want to load the Title of a web and a folder. But since the folder does not exist, the call to ClientContext.ExecuteQuery throws an exception. Even if you handle the exception, the Web.Title value is not going to be available. The way to overcome this, traditionally, is to run the code in separate batches:

using (ClientContext context = new ClientContext(contextUrl))
{
  var folder = context.Web.GetFolderByServerRelativeUrl(folderUrl);
  try
  {
    context.ExecuteQuery();
  }
  catch
  { /*do nothing*/ }
  context.Load(context.Web, w => w.Title);
  context.ExecuteQuery(); 
  Console.WriteLine(context.Web.Title);
  Console.WriteLine(folder.Name);
}

Of course, this requires two calls to the SharePoint server which incurs additional network lag. But, there is a better way. Using the ExceptionHandlingScope class. This class allows you to manage exceptions in CSOM and process try/catch code in a single call to the SharePoint server. The syntax, however, is a bit nasty:

var folderUrl = "/sites/dev/Lists/DemoList/FolderThatDoesNotExist";
var contextUrl = https://SharePointServer/sites/YourSite/;

using (ClientContext context = new ClientContext(contextUrl))
{
  var scope = new ExceptionHandlingScope(context);
  Folder folder = null;
  using (scope.StartScope())
  {
    using (scope.StartTry())
    {
      folder = context.Web.GetFolderByServerRelativeUrl(folderUrl);
      context.Load(folder);
    } 

    using (scope.StartCatch())
    {
      //Run Error Handling Code Here
      //DO NOT SET folder = null here!
    }
  } 
  context.Load(ctx.Web, w => w.Title); 
  context.ExecuteQuery();
}

To use the scope, you create a new instance of the ExceptionHandlingScope class. Then you wrap a call to StartScope in a using statement. Inside that using statement, you are required to call StartTry and StartCatch and may optionally call StartFinally. All of these calls should be wrapped in their own using statements as well. These, roughly, correspond to a try/catch/finally statement with one major caveat. All of the code from all of the statements always runs. What this means is that you must carefully manage what you do in the statements to not step on one another. For example, if you are trying to get a folder in the try section and it fails, you may be tempted to set the folder variable to null in the catch section. DO NOT DO THIS. The code in the catch section always runs on the client, so at the end of all the statements your folder variable will be null, so you won’t be able to use any information from the folder variable even if the operation succeeds completely. So you do have to carefully manage your code to watch out for how things interact together.

Now, the cool thing is that if any of the code written in the StartTry section fails, then the call to ClientContext.ExecuteQuery does not throw an exception. The exception information is stored in the ExceptionHandlingScope instance. You can check the HasException property which is a Boolean value indicating whether an exception occurred, and then there are a few additional properties containing information about the error that occurred (i.e. ServerErrorCode, ServerErrorDetails, ServerErrorTypeName, ServerErrorValue, ServerStackTrace).