IDX12729: Unable to decode the header ‘[PII]’ is hidden’

If you are still using the PnP-Sites-Core library (consider migrating to the new version: PnP Framework!) and using access tokens to connect to SharePoint, there may be scenarios where you will get the error message: IDX12729: Unable to decode the header ‘[PII]’ is hidden’ as Base64Url encoded string.

I recently found this on an Azure function connecting to SharePoint Online using an Azure AD app registration and certificate. The connection was working and also some list operations, but was getting the error above when creating a site.
Searching online we can find a few GitHub issues where this error was mentioned, like this one.

In summary, the issue is down to different DLL version requirements for Newtonsoft.Json. This could be easily fixed with assembly binding redirecting on a typical .NET solution via web.config (or app.config), but we are in Azure land…and instead, we need to consider using app settings.

After some search, I found this absolutely awesome post by Codopia. He shares some code that runs on the Function constructor (before our code is executed) and deals with the assembly versions. Plus, all the code is generic and you can configure the DDLs as an app setting in Azure!

All kudos go to Codopia, I am only sharing the code again to try to reach more people as it was not easy to find in search results.

Code

Create a class AssemblyBindingRedirectHelper.cs with the following code:


using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Json;
using System.Text;

namespace SiteProvisioning.Helpers
{

  public static class AssemblyBindingRedirectHelper
  {
    ///<summary>
    /// Reads the "BindingRedirecs" field from the app settings and applies the redirection on the
    /// specified assemblies
    /// </summary>

    public static void ConfigureBindingRedirects()
    {
      var redirects = GetBindingRedirects();
      redirects.ForEach(RedirectAssembly);
    }

    private static List<BindingRedirect> GetBindingRedirects()
    {
      var result = new List<BindingRedirect>();
      var bindingRedirectListJson = Environment.GetEnvironmentVariable("BindingRedirects");
      using (var memoryStream = new MemoryStream(Encoding.Unicode.GetBytes(bindingRedirectListJson)))
      {
        var serializer = new DataContractJsonSerializer(typeof(List<BindingRedirect>));
        result = (List<BindingRedirect>)serializer.ReadObject(memoryStream);
      }
      return result;
    }

    private static void RedirectAssembly(BindingRedirect bindingRedirect)
    {
      ResolveEventHandler handler = null;
      handler = (sender, args) =>
      {
        var requestedAssembly = new AssemblyName(args.Name);
        if (requestedAssembly.Name != bindingRedirect.ShortName)
        {
          return null;
        }
        var targetPublicKeyToken = new AssemblyName("x, PublicKeyToken=" + bindingRedirect.PublicKeyToken).GetPublicKeyToken();
        requestedAssembly.SetPublicKeyToken(targetPublicKeyToken);
        requestedAssembly.Version = new Version(bindingRedirect.RedirectToVersion);
        requestedAssembly.CultureInfo = CultureInfo.InvariantCulture;
        AppDomain.CurrentDomain.AssemblyResolve -= handler;
        return Assembly.Load(requestedAssembly);
      };
      AppDomain.CurrentDomain.AssemblyResolve += handler;
    }

    public class BindingRedirect
    {
      public string ShortName { get; set; }
      public string PublicKeyToken { get; set; }
      public string RedirectToVersion { get; set; }
    }
  }
}

Next, create a new class ApplicationHelper.cs with the following code:

namespace SiteProvisioning.Helpers
{

  public static class ApplicationHelper
  {
    private static bool IsStarted = false;
    private static object _syncLock = new object();
    ///<summary>
    /// Sets up the app before running any other code
    /// </summary>

    public static void Startup()
    {
      if (!IsStarted)
      {
        lock (_syncLock)
        {
          if (!IsStarted)
          {
            AssemblyBindingRedirectHelper.ConfigureBindingRedirects();
            IsStarted = true;
          }
        }
      }
    }
  }
}

And finally, add a constructor to the function and call the Startup method from ApplicationHelper:

namespace StormSiteProvisioning
{
  public static class ConnectSiteProvisioning
  {
    static ConnectSiteProvisioning()
    {
      ApplicationHelper.Startup();
    }

    [FunctionName("SiteProvisioning")]
    public static async Task RunAsync([QueueTrigger("siteprovisioning", Connection = "AzureWebJobsStorage")] string myQueueItem, TraceWriter log)
    {
      log.Info("Processing new queue item: '" + myQueueItem + "''", (string)null);
      QueueItem provisioningItem = JsonConvert.DeserializeObject<QueueItem>(myQueueItem);

      // ....... custom code here


    }
  }
}

Your solution is ready at this point. The only thing that is left to do is to add a new Azure app setting with the name BindingRedirects and the value below to configure the redirection

[ { "ShortName": "Newtonsoft.Json", "RedirectToVersion": "10.0.0.0", "PublicKeyToken": "30ad4fe6b2a6aeed" } ]

Again, please check the post from Codopia as he deserves all the credit 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *