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