public async Task <ActionResult> Create(string selectedSharePointList)
        {
            using (var cc = ContextProvider.GetWebApplicationClientContext(Settings.SiteCollection))
            {
                if (cc != null)
                {
                    // Hookup event to capture access token
                    cc.ExecutingWebRequest += Cc_ExecutingWebRequest;

                    ListCollection     lists           = cc.Web.Lists;
                    Guid               listId          = new Guid(selectedSharePointList);
                    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(), CloudConfigurationManager.GetSetting("WebHookEndPoint"), 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())
                    {
                        dbContext.ListWebHooks.Add(new ListWebHooks()
                        {
                            Id              = new Guid(res.Id),
                            ListId          = listId,
                            LastChangeToken = sharePointLists.FirstOrDefault().CurrentChangeToken.StringValue,
                        });
                        var saveResult = await dbContext.SaveChangesAsync();
                    }
                }
            }

            return(RedirectToAction("Index", "Home"));
        }
        public async Task <ActionResult> Delete(string id = null, string listId = null)
        {
            using (var cc = ContextProvider.GetWebApplicationClientContext(Settings.SiteCollection))
            {
                if (cc != null)
                {
                    // 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();
                            }
                        }
                    }
                }
            }

            return(RedirectToAction("Index", "Home"));
        }
        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();
                }
            };
        }
        public async Task <ActionResult> Delete(string id = null, string listId = null)
        {
            var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);

            using (var cc = spContext.CreateUserClientContextForSPHost())
            {
                if (cc != null)
                {
                    // 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();

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

                            if (webHookRow != null)
                            {
                                dbContext.ListWebHooks.Remove(webHookRow);
                                var saveResult = await dbContext.SaveChangesAsync();
                            }
                        }
                    }
                }
            }

            return(RedirectToAction($"Index", $"Home", new { SPHostUrl = Request.QueryString[$"SPHostUrl"] }));
        }
        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
            {
                #region Setup an app-only client context
                var am = new AuthenticationManager();

                var url          = $"https://{CloudConfigurationManager.GetSetting("TenantName")}{notification.SiteUrl}";
                var realm        = TokenHelper.GetRealmFromTargetUrl(new Uri(url));
                var clientId     = CloudConfigurationManager.GetSetting("ClientId");
                var clientSecret = CloudConfigurationManager.GetSetting("ClientSecret");

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

                cc.ExecutingWebRequest += Cc_ExecutingWebRequest;
                #endregion

                #region Grab the list for which the web hook was triggered
                var lists   = cc.Web.Lists;
                var listId  = new Guid(notification.Resource);
                var results = cc.LoadQuery(lists.Where(lst => lst.Id == listId));
                cc.ExecuteQueryRetry();
                var changeList = results.FirstOrDefault();
                if (changeList == null)
                {
                    // list has been deleted in between the event being fired and the event being processed
                    return;
                }
                #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
                var changeQuery = new ChangeQuery(false, true)
                {
                    Item         = true,
                    FetchLimit   = 1000, // Max value is 2000, default = 1000
                    DeleteObject = false,
                    Add          = true,
                    Update       = true,
                    SystemUpdate = false
                };

                // grab last change token from database if possible
                using (var dbContext = new SharePointWebHooks())
                {
                    ChangeToken lastChangeToken = null;
                    var         id = new Guid(notification.SubscriptionId);

                    var listWebHookRow = dbContext.ListWebHooks.Find(id);
                    if (listWebHookRow != null)
                    {
                        lastChangeToken = new ChangeToken
                        {
                            StringValue = listWebHookRow.LastChangeToken
                        };
                    }

                    // Start pulling down the changes
                    var 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
                            {
                                StringValue =
                                    $"1;3;{notification.Resource};{DateTime.Now.AddMinutes(-5).ToUniversalTime().Ticks.ToString()};-1"
                            };
                            // See https://blogs.technet.microsoft.com/stefan_gossner/2009/12/04/content-deployment-the-complete-guide-part-7-change-token-basics/
                        }

                        // 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 item is changed more than once
                        var uniqueChanges = changes.Cast <ChangeItem>().AsEnumerable().DistinctBy(change => change.ItemId).ToList();

                        if (uniqueChanges.Any())
                        {
                            foreach (var change in uniqueChanges)
                            {
                                lastChangeToken = change.ChangeToken;

                                try
                                {
                                    // do "work" with the found change
                                    DoWork(cc, changeList, change);
                                }
                                catch (Exception)
                                {
                                    // ignored
                                }
                            }

                            // We potentially can have a lot of changes so be prepared to repeat the
                            // change query in batches of 'FetchLimit' until 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 change token 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 ListWebHooks()
                        {
                            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 expiration date time 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)
                {
                    return;
                }
                var webHookManager = new WebHookManager();
                var updateResult   = Task.WhenAny(
                    webHookManager.UpdateListWebHookAsync(
                        url,
                        listId.ToString(),
                        notification.SubscriptionId,
                        CloudConfigurationManager.GetSetting("WebHookEndPoint"),
                        DateTime.Now.AddMonths(3),
                        _accessToken)
                    ).Result;

                if (updateResult.Result == false)
                {
                    throw new Exception(
                              $"The expiration date of web hook {notification.SubscriptionId} with endpoint {CloudConfigurationManager.GetSetting("WebHookEndPoint")} could not be updated");
                }
                #endregion
            }
            catch (Exception ex)
            {
                // Log error
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                // ReSharper disable once ConstantConditionalAccessQualifier
                cc?.Dispose();
            }
        }
        /// <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();
                }
            }
        }