/// <summary> /// Executes the router to find an appropriate processor for the incoming message /// </summary> /// <param name="type">The type of incoming message from Slack.</param> /// <param name="context">The <see cref="HttpContext"/> for the current request.</param> /// <param name="httpClient">The <see cref="HttpClient"/> instance to use for any network calls (to promote reuse)</param> /// <returns>A task that represents the execution of the router.</returns> /// <exception cref="ArgumentNullException">Throws when <paramref name="context"/> or <paramref name="httpClient"/> is <c>null</c>.</exception> public async Task ProcessAsync(IncomingMessageType type, HttpContext context, HttpClient httpClient) { _ = context ?? throw new ArgumentNullException(nameof(context)); _ = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); // Ensure it's a POST coming from Slack if (context.Request.Method != "POST") { context.Response.StatusCode = 404; return; } AppResponse response = null; var isRespondable = false; try { switch (type) { case IncomingMessageType.Command: isRespondable = true; response = await ProcessCommandAsync(context); break; case IncomingMessageType.Action: isRespondable = true; response = await ProcessActionAsync(context); break; case IncomingMessageType.Event: await ProcessEventAsync(context); break; } } catch (Exception e) { var message = $"An exception occurred while processing this {type}"; _logger.LogError(e, message, null); if (isRespondable) { if (response == null) { response = new AppResponse(); } response.ImmediateMessage = SlackMessage.CreateErrorMessage($"message: {e.Message}"); response.ResponseUrlMessages.Clear(); context.Response.StatusCode = 200; } else { context.Response.StatusCode = 500; } } if (!isRespondable) { return; } if (response.ImmediateMessage != null) { // Respond immediately with a message // Per Slack docs, this keeps the original message in the channel // See https://api.slack.com/slash-commands#delayed_responses_and_multiple_responses context.Response.ContentType = "application/json"; await context.Response.WriteAsync(response.ImmediateMessage.ToJson()); } if (response.ResponseUrlMessages.Count > 0 && !string.IsNullOrEmpty(response.response_url)) { foreach (var message in response.ResponseUrlMessages) { // Send the message back to the response_url // Per Slack docs, this replaces the original message in the channel // See https://api.slack.com/slash-commands#delayed_responses_and_multiple_responses var requestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(response.response_url)) { Content = new StringContent(message.ToJson(), Encoding.UTF8, "application/json") }; try { var result = await httpClient.SendAsync(requestMessage); if (!result.IsSuccessStatusCode) { throw new Exception($"Failed to post to Slack: Status Code \"{result.StatusCode}\" ({(int)result.StatusCode})."); } } catch (Exception e) { // Something happened posting back to the response_url, so log the error _logger.LogError(e, "An exception occurred while sending a message to the response_url", null); } } } }