public static async Task <HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "EventDispatch")] HttpRequestMessage req, TraceWriter log)
        {
            Log(log, $"C# HTTP trigger function processed a request! RequestUri={req.RequestUri}");
            var func = new EventDispatchHandler(req);

            func.FunctionNotify += (sender, args) => Log(log, args.Message);

            var eventDispatchFunctionArgs = new EventDispatchFunctionArgs
            {
                StorageAccount    = ConfigurationManager.AppSettings["ConfigurationStorageAccount"],
                StorageAccountKey = ConfigurationManager.AppSettings["ConfigurationStorageAccountKey"]
            };

            return(await Task.Run(() => func.Execute(eventDispatchFunctionArgs)));
        }
        public static Task <HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
        {
            Log(log, $"C# HTTP trigger function processed a request! RequestUri={req.RequestUri}");
            var func = new EventDispatchHandler(req);

            func.FunctionNotify += (sender, args) => Log(log, args.Message);

            var eventDispatchFunctionArgs = new EventDispatchFunctionArgs()
            {
                StorageAccount    = ConfigurationManager.AppSettings["ConfigurationStorageAccount"],
                StorageAccountKey = ConfigurationManager.AppSettings["ConfigurationStorageAccountKey"]
            };

            return(Task.FromResult(func.Execute(eventDispatchFunctionArgs)));
        }
        /// <summary>
        /// Processes the received event and sends the result to the client's service bus queue.
        ///
        /// SharePoint's remote event notification lacks the current item state for ItemDeleting and ItemUpdating events.
        /// For these event types, it attempts to fetch the current (unchanged) item and populate the ItemBeforeProperties. It is possible for the attempt to fail if the item is already deleted. If the attempt fails, the event is forwarded with the available information.
        /// </summary>
        /// <param name="args">An <see cref="EventDispatchFunctionArgs"/> instance specifying the location of the client configuration in Azure storage.</param>
        /// <remarks>The event is ignored if it is the result of an action taken by an app only identity</remarks>
        /// <returns>HttpStatusCode.OK if all is well or 500.</returns>
        public HttpResponseMessage Execute(EventDispatchFunctionArgs args)
        {
            try
            {
                _response.StatusCode = HttpStatusCode.OK;

                //Ignore the event if it is the result of an action taken by an app only identity
                if (_eventInfo.EventProperties.ContainsKey("UserLoginName") && _eventInfo.EventProperties["UserLoginName"].Contains(AppOnlyPrincipalId))
                {
                    Log("Event source is an app not a user. Ignoring");
                    return(_response);
                }

                var clientId = GetClientId();

                if (clientId == null)
                {
                    Log("Request has no client ID. Ignoring");
                    return(_response);
                }

                //Connect to the SharePoint site and get access tokens
                try
                {
                    _clientConfiguration = GetConfiguration(clientId, args.StorageAccount, args.StorageAccountKey);
                }
                catch
                {
                    Log("Failed to get client configuration");
                    Log($"Client Id is {clientId}");
                    Log(args.StorageAccount);
                    Log(args.StorageAccountKey);
                    throw;
                }

                var spContextToken = TokenHelper.ReadAndValidateContextToken(ContextToken, _requestAuthority, clientId,
                                                                             _clientConfiguration.AcsClientConfig.ClientSecret);
                var encodedCacheKey = TokenHelper.Base64UrlEncode(spContextToken.CacheKey);
                var spHostUri       = new Uri(SPWebUrl);

                var accessToken = TokenHelper.GetACSAccessTokens(spContextToken, spHostUri.Authority,
                                                                 _clientConfiguration.ClientId,
                                                                 _clientConfiguration.AcsClientConfig.ClientSecret);

                var ctx = ConnectToSPWeb(accessToken);

                var securityTokens = new SecurityTokens()
                {
                    ClientId           = clientId,
                    AccessToken        = accessToken.AccessToken,
                    AccessTokenExpires = accessToken.ExpiresOn,
                    AppWebUrl          = SPWebUrl,
                    Realm        = spContextToken.Realm,
                    RefreshToken = spContextToken.RefreshToken
                };

                Log($"Storing tokens for {clientId}/{encodedCacheKey}");
                StoreSecurityTokens(securityTokens, encodedCacheKey, args.StorageAccount, args.StorageAccountKey);

                //Create the event message to send to the client's service bus queue
                var eventMessage = new QueuedSharePointProcessEvent()
                {
                    SharePointRemoteEventAdapter = _eventInfo,
                    ClientId        = _clientConfiguration.ClientId,
                    AppWebUrl       = SPWebUrl,
                    UserAccessToken = accessToken.AccessToken,
                    AppAccessToken  = GetACSAccessTokens(clientId, encodedCacheKey, true),
                };

                //SharePoint's remote event notification lacks the current item state for ItemDeleting and ItemUpdating events
                //For these event types, attempt to fetch the current (unchanged) item and populate the ItemBeforeProperties
                if (_eventInfo.EventType == "ItemDeleting" || _eventInfo.EventType == "ItemUpdating")
                {
                    //SharePoint feature provisioning sometimes raises this event
                    //and deletes some things in the process with no ListId given
                    var listId = Guid.Parse(_eventInfo.EventProperties["ListId"]);
                    if (listId != default(Guid))
                    {
                        var item =
                            ctx.Web.Lists.GetById(Guid.Parse(_eventInfo.EventProperties["ListId"]))
                            .GetItemById(_eventInfo.EventProperties["ListItemId"]);
                        ctx.Load(item, i => i.FieldValuesAsText);
                        try
                        {
                            ctx.ExecuteQueryRetry();
                            _eventInfo.ItemBeforeProperties = item.FieldValuesAsText.FieldValues;
                        }
                        catch
                        {
                            //The query depends on timing and there are a number of things that can go wrong.
                            //If the BeforeProperties can't be read, forward the event anyway with the info that is available
                        }
                    }
                }

                //Send the event to the client's service bus queue
                SendQueueMessage(eventMessage);
            }
            catch (Exception ex)
            {
                Log(ex.ToString());
                throw;
            }

            return(_response);
        }