简体   繁体   English

Azure Function 和 SharePoint webhook:未从 Z97F02F6993B93736DFEZ0D 列表中获取更改

[英]Azure Function and SharePoint webhook: not getting changes from SharePoint list

I'm using a couple of Azure Functions with SharePoint webhook.我正在使用几个 Azure 函数和 SharePoint webhook。

The first function is the one used to save messages from SharePoint webhook to a queue (Azure storage queue).第一个 function 用于将消息从 SharePoint webhook 保存到队列(Azure 存储队列)。 This is the function content:这是function内容:

    [FunctionName("QueueFunction")]
    public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
    {
         log.Info($"Webhook was triggered!");

        // Grab the validationToken URL parameter
        string validationToken = req.GetQueryNameValuePairs()
            .FirstOrDefault(q => string.Compare(q.Key, "validationtoken", true) == 0)
            .Value;

        // If a validation token is present, we need to respond within 5 seconds by  
        // returning the given validation token. This only happens when a new 
        // web hook is being added
        if (validationToken != null)
        {
            log.Info($"Validation token {validationToken} received");
            var response = req.CreateResponse(HttpStatusCode.OK);
            response.Content = new StringContent(validationToken);
            return response;
        }

        log.Info($"SharePoint triggered our webhook...great :-)");
        var content = await req.Content.ReadAsStringAsync();
        log.Info($"Received following payload: {content}");

        var notifications = JsonConvert.DeserializeObject<ResponseModel<NotificationModel>>(content).Value;
        log.Info($"Found {notifications.Count} notifications");

        if (notifications.Count > 0)
        {
            // get the cloud storage account
            string queueName = "MYQUEUE";
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("AzureWebJobsStorage"));
            CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
            CloudQueue queue = queueClient.GetQueueReference(queueName);
            await queue.CreateIfNotExistsAsync();

            // store each notification as a queue item
            foreach (var notification in notifications)
            {
                string message = JsonConvert.SerializeObject(notification);
                log.Info($"Adding to {queueName}: {message}");
                await queue.AddMessageAsync(new CloudQueueMessage(message));
                log.Info($"added.");
            }

        // if we get here we assume the request was well received
        return new HttpResponseMessage(HttpStatusCode.OK);
    }

The message in queue is correctly added.队列中的消息已正确添加。

Then I've another function triggered by queue.然后我有另一个由队列触发的 function 。 This is the code of the function:这是 function 的代码:

[FunctionName("OCRFunction")]
    public static void Run([QueueTrigger("MYQUEUE", Connection = "QueueConn")]string myQueueItem, TraceWriter log)
    {
        log.Info($"C# Queue trigger function processed: {myQueueItem}");
        string siteUrl = "https://MYSHAREPOINT.sharepoint.com/sites/MYSITE";
        log.Info($"Processing notifications...");
        string json = myQueueItem;
        var data = (JObject)JsonConvert.DeserializeObject(json);
        string notificationResource = data["resource"].Value<string>();
        
        ClientContext SPClientContext = LoginSharePoint(siteUrl);
        log.Info($"Logged in SharePoint");
        GetChanges(SPClientContext, notificationResource, log);
       
    }

    public static ClientContext LoginSharePoint(string BaseUrl)
    {
        // Login using UserOnly Credentials (User Name and User PW)
        ClientContext cntReturn;

        string myUserName = config["spUN"];
        string myPassword = config["spPWD"];

        SecureString securePassword = new SecureString();
        foreach (char oneChar in myPassword) securePassword.AppendChar(oneChar);
        SharePointOnlineCredentials myCredentials = new SharePointOnlineCredentials(myUserName, securePassword);

        cntReturn = new ClientContext(BaseUrl);
        cntReturn.Credentials = myCredentials;

        return cntReturn;
    }

    static void GetChanges(ClientContext SPClientContext, string ListId, TraceWriter log)
    {
        Web spWeb = SPClientContext.Web;
        List myList = spWeb.Lists.GetByTitle("MY LIST");
        SPClientContext.Load(myList);
        SPClientContext.ExecuteQuery();

        ChangeQuery myChangeQuery = GetChangeQueryNew(ListId);

        var allChanges = myList.GetChanges(myChangeQuery);
        SPClientContext.Load(allChanges);
        SPClientContext.ExecuteQuery();

        log.Info($"---- Changes found : " + allChanges.Count());
        foreach (Change oneChange in allChanges)
        {
            if (oneChange is ChangeItem)
            {
                int myItemId = (oneChange as ChangeItem).ItemId;

                log.Info($"---- Changed ItemId : " + myItemId);
                ListItem myItem = myList.GetItemById(myItemId);
                Microsoft.SharePoint.Client.File myFile = myItem.File;
                ClientResult<System.IO.Stream> myFileStream = myFile.OpenBinaryStream();
                SPClientContext.Load(myFile);
                SPClientContext.ExecuteQuery();

                byte[] myFileBytes = ConvertStreamToByteArray(myFileStream);
                [...] SOME CODE HERE [...]
                myItem["OCRText"] = myText;
                myItem.Update();
                SPClientContext.ExecuteQuery();
                log.Info($"---- Text Analyze OCR added to SharePoint Item");
            }
        }
    }

    public static ChangeQuery GetChangeQueryNew(string ListId)
    {
        ChangeToken lastChangeToken = new ChangeToken();
        lastChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.AddMinutes(-1).ToUniversalTime().Ticks.ToString());
        ChangeToken newChangeToken = new ChangeToken();
        newChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.ToUniversalTime().Ticks.ToString());
        ChangeQuery myChangeQuery = new ChangeQuery(false, false);
        myChangeQuery.Item = true;  // Get only Item changes
        myChangeQuery.Add = true;   // Get only the new Items
        myChangeQuery.ChangeTokenStart = lastChangeToken;
        myChangeQuery.ChangeTokenEnd = newChangeToken;

        return myChangeQuery;
    }

    public static Byte[] ConvertStreamToByteArray(ClientResult<System.IO.Stream> myFileStream)
    {
        Byte[] bytReturn = null;

        using (System.IO.MemoryStream myFileMemoryStream = new System.IO.MemoryStream())
        {
            if (myFileStream != null)
            {
                myFileStream.Value.CopyTo(myFileMemoryStream);
                bytReturn = myFileMemoryStream.ToArray();
            }
        }

        return bytReturn;
    }

    public static async Task<TextAnalyzeOCRResult> GetAzureTextAnalyzeOCR(byte[] myFileBytes)
    {
        TextAnalyzeOCRResult resultReturn = new TextAnalyzeOCRResult();

        HttpClient client = new HttpClient();

        client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "XXXXXXXXXXXXXXXXXXXX");
        string requestParameters = "language=unk&detectOrientation=true";

        /* OCR API */
        string uri = "https://MYOCRSERVICE.cognitiveservices.azure.com/vision/v3.0/ocr" + "?" + requestParameters;

        string contentString = string.Empty;

        HttpResponseMessage response;

        using (ByteArrayContent content = new ByteArrayContent(myFileBytes))
        {
            content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

            response = await client.PostAsync(uri, content);

            contentString = await response.Content.ReadAsStringAsync();

            resultReturn = JsonConvert.DeserializeObject<TextAnalyzeOCRResult>(contentString);
            return resultReturn;
        }
    }

Before current approach with two functions, I was using a single function where I managed the notifications and I executed some code to update a field in my SharePoint list.在当前使用两个函数的方法之前,我使用单个 function 管理通知并执行一些代码来更新 SharePoint 列表中的字段。 This method was having some problem when I was receiving many notifications from SharePoint so I decided to use queue as suggested in Microsoft documentation.当我收到来自 SharePoint 的许多通知时,此方法出现了一些问题,因此我决定按照 Microsoft 文档中的建议使用队列。 This solution was working fine with a single notification received and my SharePoint list item were updated without problem.此解决方案在收到单个通知时运行良好,并且我的 SharePoint 列表项已毫无问题地更新。

To avoid problems with multiple notification, I decided to split functions, one registering notifications in a queue and the other executing some operations and updating a SharePoint field.为了避免多个通知的问题,我决定拆分功能,一个在队列中注册通知,另一个执行一些操作并更新 SharePoint 字段。 The first one function QueueFunction is working fine, the second one is triggering correctly but it is not getting changes from SharePoint list even if I just add one item.第一个 function QueueFunction工作正常,第二个触发正确,但即使我只添加一个项目,它也没有从 SharePoint 列表中获得更改。 I've tried to check GetChanges code to find why it is always returning no changes, but the code is the same of the one I used when I had only one function, so I can't understand why the behaviour is changed.我试图检查GetChanges代码以找出为什么它总是不返回任何更改,但代码与我只有一个 function 时使用的代码相同,所以我不明白为什么行为会改变。

What's wrong with my approach?我的方法有什么问题? Is there something I could do to correct the second function?我可以做些什么来纠正第二个 function?

According to the comments, just summarize the solution as below for other communities reference:根据评论,将解决方案总结如下,供其他社区参考:

Use a function to save the message in a queue and then call an azure web job, the problem was caused by the the running time of the function may exceed 5 minutes. Use a function to save the message in a queue and then call an azure web job, the problem was caused by the the running time of the function may exceed 5 minutes.

By the way, the default timeout of azure function(with consumption plan) is 5 minutes, we can see all of the default timeout for different plan on this page (also shown as below screenshot).顺便说一句,azure 函数(有消费计划)的默认超时时间是 5 分钟,我们可以在这个页面上看到不同计划的所有默认超时时间(也如下图所示)。

在此处输入图像描述

If we want longer timeout, we can set the functionTimeout property in host.json of the function(but can not exceed the Maximum timeout).如果我们想要更长的超时时间,我们可以在函数的host.json中设置functionTimeout属性(但不能超过Maximum timeout)。 Or we can also use higher plan for the function app, such as Premium plan and App Service plan.或者我们也可以为 function 应用程序使用更高的计划,例如高级计划和应用服务计划。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM