0

I'd like my Azure Function to be triggered on Azure Queue events, and use the event data when communicating with SharePoint Online. The first SharePoint task would be to provision new sites based on the incoming Queue data.

The complete code is added to the bottom of this post.

I've set up a new Visual Studio 2019 Azure Function v1 function, and installed Microsoft.NET.Sdk.Functions v1.0.24, Newtonsoft.Json v11.0.2 and SharePointPnPCoreOnline v3.15.1911.

After fighting the very infamous Could not load file or assembly 'Newtonsoft.Json, Version=11.0.2, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. error message, I came across this blog post suggesting I should use binding redirects. I've set up my project according to the blog post, but still struggle with the above error message. Here's a larger portion of the error message:

[15.11.2019 07:54:02] Something went wrong: System.ArgumentException: IDX12729: Unable to decode the header '[PII is hidden]' as Base64Url encoded string. jwtEncodedString: '[PII is hidden]'. ---> System.TypeInitializationException: The type initializer for 'System.IdentityModel.Tokens.Jwt.JsonExtensions' threw an exception. ---> System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The system cannot find the file specified. ---> System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=11.0.2, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The system cannot find the file specified. [15.11.2019 07:54:02] at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) [15.11.2019 07:54:02] at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) [15.11.2019 07:54:02] at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) [15.11.2019 07:54:02] at System.Reflection.Assembly.Load(AssemblyName assemblyRef) [15.11.2019 07:54:02] at FunctionApp5.Application.AssemblyBindingRedirectHelper.<>c__DisplayClass2_0.b__0(Object sender, ResolveEventArgs args) [15.11.2019 07:54:02] at System.AppDomain.OnAssemblyResolveEvent(RuntimeAssembly assembly, String assemblyFullName) [15.11.2019 07:54:02] --- End of inner exception stack trace --- [15.11.2019 07:54:02] at System.IdentityModel.Tokens.Jwt.JsonExtensions..cctor() [15.11.2019 07:54:02] --- End of inner exception stack trace --- [15.11.2019 07:54:02] at System.IdentityModel.Tokens.Jwt.JsonExtensions.DeserializeJwtHeader(String jsonString) [15.11.2019 07:54:02] at System.IdentityModel.Tokens.Jwt.JwtSecurityToken.Decode(String[] tokenParts, String rawData)

I noticed that the error message first referst to the version 10.0.0.0 of Newtonsoft.JSON, and then referst to the version 11.0.2. So it looks to me like the bindings redirect actually kicks in, but that the runtime can't find version 11.0.2 even though it's installed.

Extract from the debug log:

RedirectAssembly()
Authenticate to SharePoint 
Creating SharePoint site
Binding Newtonsoft.Json to version 11.0.2

Another thing I've noticed, is that the actual bindings seems to be performed after my business code (i.e ClientContext's CreateSiteAsync function, that creates the actual site) have executed. I base this statements off the fact that when reading through the debug output, the line that says Binding Newtonsoft.Json to version 11.0.2 comes after the line Creating SharePoint site. So I'm thinking that if I find some way of running the actual binding redirects before the business code is run, maybe things will work.

Any help in debugging this issue will be greatly appreciated.

The code

class "AssemblyBindingRedirectHelper"

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

namespace FunctionApp5.Application
{
    public static class AssemblyBindingRedirectHelper
    {

        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)
        {

            Debug.WriteLine("RedirectAssembly() ");
            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;

                Debug.WriteLine("Binding " + bindingRedirect.ShortName + " to version " + bindingRedirect.RedirectToVersion);

                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; }
        }
    }
}

class "ApplicationHelper"

using System;
using System.Diagnostics;

public static class ApplicationHelper
{
    private static bool IsStarted = false;
    private static object _syncLock = new object();

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

The business logic class

I made a small modification to the code, based on this pattern, but I believe the effect should be pretty much the same.

using System;
using System.Diagnostics;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.SharePoint.Client;
using OfficeDevPnP.Core.Sites;

namespace FunctionAppBootstrapping
{

    public static class Bootstrapping
    {
        public static void Init()
        {
            ApplicationHelper.Startup();
        }
    }

    public static class Function1
    {

        [FunctionName("Function1")]
        public static async void Run([QueueTrigger("queue001", Connection = "")]string myQueueItem, TraceWriter log)
        {
            Bootstrapping.Init();
            log.Info($"C# Queue trigger function processed: {myQueueItem}");

            var azureClientId = "myID";
            var azureClientSecret = "mySecret";
            string siteUrl = "https://<mytenant>.sharepoint.com";

            Debug.WriteLine("Authenticate to SharePoint " + System.Threading.Thread.CurrentThread.Name);
            using (var cc = new OfficeDevPnP.Core.AuthenticationManager().GetAppOnlyAuthenticatedContext(siteUrl, azureClientId, azureClientSecret))
            {
                var url = siteUrl + "/sites/" + myQueueItem;

                CommunicationSiteCollectionCreationInformation communicationSiteInfo = new CommunicationSiteCollectionCreationInformation
                {
                    Title = "My new site name",
                    Url = url,
                    SiteDesign = CommunicationSiteDesign.Blank,
                    Description = "My description",
                    Owner = "akeho@devmiljo.no"
                };

                try
                {
                    Debug.WriteLine("Creating SharePoint site");
                    var createCommSite = await cc.CreateSiteAsync(communicationSiteInfo);
                    log.Info($"Successfully created the page {url}. Return value: {createCommSite}");
                }
                catch (Exception e)
                {
                    log.Error($"Something went wrong: {e.ToString()}");
                }
            };

        }
    }
}
  • I am facing the same issue. Have you found a solution?Br – mJay Jan 28 at 11:37
  • For me it was an assembly binding issue. It seems like the wrong version of Newtonsoft has been bound in the context of the Azure Function. As this is supported as of runtime version 2 but I had to use PnP which only works on version 1, I had to take this custom assembly binder helper: codopia.wordpress.com/2017/07/21/… – mJay Jan 28 at 12:39
  • @mJay, I decided to run the code on an on-premise server instead of running in in Azure Functions. – kenneho Mar 10 at 6:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.