private void RegisterAddingWebHookEvent(ClientContext cc)
        {
            btnCreate.Click += async(s, args) =>
            {
                // Hookup event to capture access token
                cc.ExecutingWebRequest += Cc_ExecutingWebRequest;

                var  lists  = cc.Web.Lists;
                Guid listId = new Guid(ListDropDown.SelectedItem.Text.Split(new string[] { "||" }, StringSplitOptions.None)[1]);
                IEnumerable <List> sharePointLists = cc.LoadQuery <List>(lists.Where(lst => lst.Id == listId));
                cc.Load(cc.Web, w => w.Url);
                cc.ExecuteQueryRetry();

                WebHookManager webHookManager = new WebHookManager();
                var            res            = await webHookManager.AddListWebHookAsync(cc.Web.Url, listId.ToString(),
                                                                                         "https://pnpwebhooksdemo.azurewebsites.net/api/webhooks", this.accessToken);

                // persist the latest changetoken of the list when we create a new webhook. This allows use to only grab the changes as of web hook creation when the first notification comes in
                using (SharePointWebHooks dbContext = new SharePointWebHooks("pnpwebhooksdemoEntities"))
                {
                    dbContext.ListWebHooks.Add(new ListWebHook()
                    {
                        Id              = new Guid(res.Id),
                        StartingUrl     = cc.Web.Url,
                        ListId          = listId,
                        LastChangeToken = sharePointLists.FirstOrDefault().CurrentChangeToken.StringValue,
                    });
                    var saveResult = await dbContext.SaveChangesAsync();
                }
            };
        }
        private async Task ReactToWebHookDeletion()
        {
            string target = Request["__EVENTTARGET"];

            if (target == "deletewebhook")
            {
                using (var cc = spContext.CreateAppOnlyClientContextForSPAppWeb())
                {
                    string[] parameters = Request["__EVENTARGUMENT"].Split(new string[] { "||" }, StringSplitOptions.None);
                    string   id         = parameters[0];
                    string   listId     = parameters[1];

                    // Hookup event to capture access token
                    cc.ExecutingWebRequest += Cc_ExecutingWebRequest;
                    // Just load the Url property to trigger the ExecutingWebRequest event handler to fire
                    cc.Load(cc.Web, w => w.Url);
                    cc.ExecuteQueryRetry();

                    WebHookManager webHookManager = new WebHookManager();
                    // delete the web hook
                    if (await webHookManager.DeleteListWebHookAsync(cc.Web.Url, listId, id, this.accessToken))
                    {
                        using (SharePointWebHooks dbContext = new SharePointWebHooks())
                        {
                            var webHookRow = await dbContext.ListWebHooks.FindAsync(new Guid(id));

                            if (webHookRow != null)
                            {
                                dbContext.ListWebHooks.Remove(webHookRow);
                                var saveResult = await dbContext.SaveChangesAsync();
                            }
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Processes a received notification. This typically is triggered via an Azure Web Job that reads the Azure storage queue
        /// </summary>
        /// <param name="notification">Notification to process</param>
        public void ProcessNotification(NotificationModel notification)
        {
            ClientContext cc = null;

            try
            {
                string url    = null;
                Guid   listId = new Guid(notification.Resource);
                Guid   id     = new Guid(notification.SubscriptionId);

                // grab last change token from database if possible
                using (SharePointWebHooks dbContext = new SharePointWebHooks("pnpwebhooksdemoEntities"))
                {
                    var listWebHookRow = dbContext.ListWebHooks.Find(id);

                    #region Setup an app-only client context
                    AuthenticationManager am = new AuthenticationManager();
                    string startingUrl       = WebConfigurationManager.AppSettings["TenantName"];
                    url = String.Format("https://{0}{1}", startingUrl, notification.SiteUrl);
                    string realm = TokenHelper.GetRealmFromTargetUrl(new Uri(url));

                    if (listWebHookRow != null)
                    {
                        startingUrl = listWebHookRow.StartingUrl;
                        url         = string.Format(startingUrl + notification.SiteUrl);
                    }

                    string clientId     = WebConfigurationManager.AppSettings["ClientId"];
                    string clientSecret = WebConfigurationManager.AppSettings["ClientSecret"];

                    if (new Uri(url).DnsSafeHost.Contains("spoppe.com"))
                    {
                        cc = am.GetAppOnlyAuthenticatedContext(url, realm, clientId, clientSecret, acsHostUrl: "windows-ppe.net", globalEndPointPrefix: "login");
                    }
                    else
                    {
                        cc = am.GetAppOnlyAuthenticatedContext(url, clientId, clientSecret);
                    }

                    cc.ExecutingWebRequest += Cc_ExecutingWebRequest;
                    #endregion

                    #region Grab the list for which the web hook was triggered
                    ListCollection lists = cc.Web.Lists;



                    var changeList = cc.Web.Lists.GetById(listId);
                    var listItems  = changeList.GetItems(new Microsoft.SharePoint.Client.CamlQuery());
                    cc.Load(changeList, lst => lst.Title, lst => lst.DefaultViewUrl);
                    cc.Load(listItems, i => i.Include(item => item.DisplayName,
                                                      item => item.Id));
                    cc.ExecuteQuery();

                    if (changeList == null)
                    {
                        // list has been deleted inbetween the event being fired and the event being processed
                        return;
                    }
                    #endregion

                    #region Grab the list used to write the web hook history
                    // Ensure reference to the history list, create when not available
                    List historyList = cc.Web.GetListByTitle("WebHookHistory");
                    if (historyList == null)
                    {
                        historyList = cc.Web.CreateList(ListTemplateType.GenericList, "WebHookHistory", false);
                    }
                    #endregion

                    #region Grab the list changes and do something with them
                    // grab the changes to the provided list using the GetChanges method
                    // on the list. Only request Item changes as that's what's supported via
                    // the list web hooks
                    ChangeQuery changeQuery = new ChangeQuery(false, true);
                    changeQuery.Item       = true;
                    changeQuery.FetchLimit = 1000; // Max value is 2000, default = 1000


                    ChangeToken lastChangeToken = null;
                    if (listWebHookRow != null)
                    {
                        lastChangeToken             = new ChangeToken();
                        lastChangeToken.StringValue = listWebHookRow.LastChangeToken;
                    }

                    // Start pulling down the changes
                    bool allChangesRead = false;
                    do
                    {
                        // should not occur anymore now that we record the starting change token at
                        // subscription creation time, but it's a safety net
                        if (lastChangeToken == null)
                        {
                            lastChangeToken = new ChangeToken();
                            // See https://blogs.technet.microsoft.com/stefan_gossner/2009/12/04/content-deployment-the-complete-guide-part-7-change-token-basics/
                            lastChangeToken.StringValue = string.Format("1;3;{0};{1};-1", notification.Resource, DateTime.Now.AddMinutes(-5).ToUniversalTime().Ticks.ToString());
                        }

                        // Assign the change token to the query...this determines from what point in
                        // time we'll receive changes
                        changeQuery.ChangeTokenStart = lastChangeToken;

                        // Execute the change query
                        var changes = changeList.GetChanges(changeQuery);
                        cc.Load(changes);
                        cc.ExecuteQueryRetry();

                        if (changes.Count > 0)
                        {
                            foreach (Change change in changes)
                            {
                                lastChangeToken = change.ChangeToken;

                                if (change is ChangeItem)
                                {
                                    // do "work" with the found change
                                    DoWork(cc, changeList, historyList, change);
                                }
                            }

                            // We potentially can have a lot of changes so be prepared to repeat the
                            // change query in batches of 'FetchLimit' untill we've received all changes
                            if (changes.Count < changeQuery.FetchLimit)
                            {
                                allChangesRead = true;
                            }
                        }
                        else
                        {
                            allChangesRead = true;
                        }
                        // Are we done?
                    } while (allChangesRead == false);

                    // Persist the last used changetoken as we'll start from that one
                    // when the next event hits our service
                    if (listWebHookRow != null)
                    {
                        // Only persist when there's a change in the change token
                        if (!listWebHookRow.LastChangeToken.Equals(lastChangeToken.StringValue, StringComparison.InvariantCultureIgnoreCase))
                        {
                            listWebHookRow.LastChangeToken = lastChangeToken.StringValue;
                            dbContext.SaveChanges();
                        }
                    }
                    else
                    {
                        // should not occur anymore now that we record the starting change token at
                        // subscription creation time, but it's a safety net
                        dbContext.ListWebHooks.Add(new ListWebHook()
                        {
                            Id              = id,
                            ListId          = listId,
                            LastChangeToken = lastChangeToken.StringValue,
                        });
                        dbContext.SaveChanges();
                    }
                }
                #endregion

                #region "Update" the web hook expiration date when needed
                // Optionally add logic to "update" the expirationdatetime of the web hook
                // If the web hook is about to expire within the coming 5 days then prolong it
                if (notification.ExpirationDateTime.AddDays(-5) < DateTime.Now)
                {
                    WebHookManager webHookManager = new WebHookManager();
                    Task <bool>    updateResult   = Task.WhenAny(
                        webHookManager.UpdateListWebHookAsync(
                            url,
                            listId.ToString(),
                            notification.SubscriptionId,
                            WebConfigurationManager.AppSettings["WebHookEndPoint"],
                            DateTime.Now.AddMonths(3),
                            this.accessToken)
                        ).Result;

                    if (updateResult.Result == false)
                    {
                        throw new Exception(String.Format("The expiration date of web hook {0} with endpoint {1} could not be updated",
                                                          notification.SubscriptionId,
                                                          WebConfigurationManager.AppSettings["WebHookEndPoint"]));
                    }
                }
                #endregion
            }
            catch (Exception ex)
            {
                // Log error
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                if (cc != null)
                {
                    cc.Dispose();
                }
            }
        }
        private async Task ExecuteWebHooksLogic()
        {
            using (var cc = spContext.CreateAppOnlyClientContextForSPAppWeb())
            {
                cc.ExecutingWebRequest += Cc_ExecutingWebRequest;

                var lists = cc.Web.Lists;
                cc.Load(cc.Web, w => w.Url);
                cc.Load(lists, l => l.Include(p => p.Title, p => p.Id, p => p.Hidden));
                cc.ExecuteQueryRetry();

                WebHookManager webHookManager = new WebHookManager();

                // Grab the current lists
                List <SharePointList>    modelLists = new List <SharePointList>();
                List <SubscriptionModel> webHooks   = new List <SubscriptionModel>();

                foreach (var list in lists)
                {
                    if (!list.Hidden)
                    {
                        modelLists.Add(new SharePointList()
                        {
                            Title = list.Title, Id = list.Id
                        });
                        var existingWebHooks = await webHookManager.GetListWebHooksAsync(cc.Web.Url, list.Id.ToString(), this.accessToken);

                        if (existingWebHooks.Value.Count > 0)
                        {
                            foreach (var existingWebHook in existingWebHooks.Value)
                            {
                                webHooks.Add(existingWebHook);
                            }
                        }
                    }
                }

                SharePointSiteModel sharePointSiteModel = new SharePointSiteModel();
                sharePointSiteModel.Lists    = modelLists;
                sharePointSiteModel.WebHooks = webHooks;
                sharePointSiteModel.SelectedSharePointList = modelLists[0].Id;


                phWebHookTable.Controls.Clear();
                if (sharePointSiteModel.WebHooks.Count() == 0)
                {
                    phWebHookTable.Controls.Add(new Literal()
                    {
                        Text = "No web hooks..."
                    });
                }
                else
                {
                    StringBuilder sb = new StringBuilder();
                    sb.Append("<table><tr><th>Actions</th><th>ID</th><th>List name</th><th>Notification URL</th><th>Expiration time</th></tr>");
                    foreach (var webHook in sharePointSiteModel.WebHooks)
                    {
                        var    list     = sharePointSiteModel.Lists.Where(f => f.Id == new Guid(webHook.Resource)).FirstOrDefault();
                        string listName = "";
                        if (list != null)
                        {
                            listName = String.Format("{0} - {1}", list.Title, webHook.Resource);
                        }
                        sb.Append($"<tr>");
                        sb.Append($"<td><a href='javascript:__doPostBack(\"deletewebhook\",\"{webHook.Id}||{list.Id.ToString("D")}\");'>Delete</a></td>");
                        sb.Append($"<td>{webHook.Id}</td>");
                        sb.Append($"<td>{listName}</td>");
                        sb.Append($"<td>{webHook.NotificationUrl}</td>");
                        sb.Append($"<td>{webHook.ExpirationDateTime}</td>");
                        sb.Append($"</tr>");
                    }
                    sb.Append("</table>");
                    phWebHookTable.Controls.Add(new Literal()
                    {
                        Text = sb.ToString()
                    });
                }

                ListDropDown.DataSource = from l in sharePointSiteModel.Lists
                                          select new System.Web.UI.WebControls.ListItem()
                {
                    Value = l.Id.ToString("D"), Text = l.Title + "||" + l.Id.ToString("D")
                };
                ListDropDown.DataBind();
            }
        }