ASP.NET Core - Slack Slash Commands
C# Slack API Integrations
Background
Most of us are all “slackers”, meaning we truly do spend a significant amount of time using Slack.
Slack is a collaboration hub for work, no matter what work you do. Itβs a place where conversations happen, decisions are made, and information is always at your fingertips. www.slack.com
It’s wildly popular in the Developer Community! In fact, almost to a fault…people are constantly sharing their “slack fatigue”. I am personally a part of roughly twenty slack workspaces. One of the really cool features of slack is the ability to integrate with the tool itself through the Slack API. Imagine a plugin playground where you’re free to extend the capabilities of the slack ecosystem. Look to the Slack API .
Slash Commands
A slack “slash command” is a command that slack enables when typing a leading /
into the chat message input. It displays an autocomplete (or pseudo Intellisense) with the available slash commands that exist in the current workspace. You add slash commands to a workspace that you’re a member of via the settings. We’ll cover that in more detail a bit later, let’s focus on building the application now that we have an understanding of the integration capabilities.
ASP.NET Core Web API
We’re building an ASP.NET Core Web API application. We’ll expose a few bits of functionality for jokes, weather and bit.ly
integration to shorten urls. The slash commands are explained below.
/joke
random nerdy Chuck Norris joke and/joke [share]
(shares the joke in the channel)/weather [zip-code]
returns detailed weather with corresponding emoji – from open weather API/shortenUrl [longUrl]
shortens a long URL, relies on bit.ly API
We will need to add a few configurations, services, models and routes to satisfy the desired functionality.
Prerequisites
The internet chuck norris database is one of my goto APIs for demonstrations. It doesn’t require an API key, nor anything else. However, several of the other bits of functionality require registering for the API.
- Bit.ly API : sign up and get your API key
- Open Weather API : sign up and generate an API key
With these various API keys, we’ll need to add a few environment variables. From the command line, execute the following commands.
setx BitlyOptions__Login [Bit.ly Login]
setx BitlyOptions__ApiKey [Bit.ly API Key]
setx OpenWeatherMapOptions__Key [Open Weather Key]
ProTip You will need to restart Visual Studio in order for the newly added environment variables to be available.
Slack API - Slash Command Request
Let’s take a look at an example controller, the WeatherController
.
using System;
using System.Threading.Tasks;
using IEvangelist.Slack.SlashCommands.Configuration;
using IEvangelist.Slack.SlashCommands.Extensions;
using IEvangelist.Slack.SlashCommands.Interfaces;
using IEvangelist.Slack.SlashCommands.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace IEvangelist.Slack.SlashCommands.Controllers
{
[ApiController, Route("api/weather")]
public class WeatherController : ControllerBase
{
readonly IWeatherService _weatherService;
readonly SlackOptions _slackOptions;
public WeatherController(
IWeatherService weatherService,
IOptions<SlackOptions> slackOptions)
{
_weatherService = weatherService;
_slackOptions =
slackOptions?.Value
?? throw new ArgumentNullException(nameof(slackOptions));
}
[
HttpPost,
Consumes("application/x-www-form-urlencoded"),
Produces("application/json")
]
public async Task<ActionResult> Weather(
[FromForm] SlackCommandRequest request)
{
var response = await _weatherService.GetWeatherAsync(request.Text);
return new JsonResult(new
{
text = response.ToString()
});
}
}
}
There are a few noteworthy considerations here. First, the Weather
action is an HTTP Post that consumes application/x-www-form-urlencoded
and produces application/json
. From the post’s form, we’ll deserialize into the SlackCommandRequest
object that looks like this.
using Newtonsoft.Json;
namespace IEvangelist.Slack.SlashCommands.Models
{
public class SlackCommandRequest
{
public string Token { get; set; }
[JsonProperty("team_id")]
public string TeamId { get; set; }
[JsonProperty("team_domain")]
public string TeamDomain { get; set; }
[JsonProperty("enterprise_id")]
public string EnterpriseId { get; set; }
[JsonProperty("enterprise_name")]
public string EnterpriseName { get; set; }
[JsonProperty("channel_id")]
public string ChannelId { get; set; }
[JsonProperty("channel_name")]
public string ChannelName { get; set; }
[JsonProperty("user_id")]
public string UserId { get; set; }
[JsonProperty("user_name")]
public string UserName { get; set; }
public string Command { get; set; }
public string Text { get; set; }
[JsonProperty("response_url")]
public string ResponseUrl { get; set; }
[JsonProperty("trigger_id")]
public string TriggerId { get; set; }
}
}
This is the shape of the object that slack’s API returns for all slash commands.
Disclaimer I attempted to verify the slack request, however; I couldn’t get it working. See my attempt verifying requests from slack for more details and
SlackCommandRequestExtensions
.
π¨ HttpClient
Our application will need to make external API calls, we will use the HttpClient
to do this. To help alleviate the concern of socket exhaustion when using HttpClient
the ASP.NET Core has middleware for offering up an IHttpClientFactory
. The IHttpClientFactory
can create HttpClient
instances using the appropriate constructor.
In the Startup.ConfigureServices
method, we’ll add the HttpClient
by invoking the .AddHttpClient
extension. This wires up all the services for resolving an instance of IHttpClientFactory
. Then we will add some πͺ-typed clients for the WeatherService
and UrlService
. We configure the typed clients by specifying their base address.
Registering Services
As previously mentioned, we have several primary intents for this application. We register some of the services differently, but it’s good to see this as it exemplifies the various capabilities.
// Joke Service
services.AddHttpClient();
services.AddTransient<IJokeService, JokeService>();
// Weather Service
services.AddHttpClient<IWeatherService, WeatherService>(
client => client.BaseAddress = new Uri("https://api.openweathermap.org/data/2.5/weather"));
// URL Service
services.AddHttpClient<IUrlService, UrlService>(
client => client.BaseAddress = new Uri("https://api-ssl.bitly.com/v3/"));
Example Service
This is the implementation of the IJokeService
which demonstrates how simple the consumption of the HttpClient
is and how to easily call out to an external API. This pattern is essentially repeated in the implementations of the IUrlService
and the IWeatherService
, but they’re calling out to different APIs with different parameters and mapping to the appropriate response type.
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using IEvangelist.Slack.SlashCommands.Extensions;
using IEvangelist.Slack.SlashCommands.Interfaces;
using IEvangelist.Slack.SlashCommands.Models;
namespace IEvangelist.Slack.SlashCommands.Services
{
public class JokeService : IJokeService
{
readonly IList<string> _positiveEmoji = new List<string>
{
":smile:",
":smirk:",
":clap:",
":joy:",
":grin:",
":yum:",
":sweat_smile:",
":laughing:",
":smiley:",
":rolling_on_the_floor_laughing:"
};
readonly IHttpClientFactory _httpClientFactory;
public JokeService(IHttpClientFactory httpClientFactory)
=> _httpClientFactory = httpClientFactory;
public async Task<JokeResponse> GetJokeAsync()
{
var client = _httpClientFactory.CreateClient();
var json =
await client.GetStringAsync(
"https://api.icndb.com/jokes/random?limitTo=[nerdy]");
return json.To<JokeResponse>();
}
public string GetRandomJokeEmoji()
=> _positiveEmoji.RandomElement();
}
}
This example service didn’t map the .BaseAddress
so we use a fully qualified request URL. Additionally, this service provides a method for getting a random positive emoji that we’ll append to the message when we respond to the slack slash command request.
Putting It All Together
With the application written, published to Azure App Service and integrated with Slack - we can start using it immediately. Open the Slack API Apps - Console and “Create New App”. Give your app a name and select the target workspace you’d like to target.
Next, under “Features and Functionality” select “Slash Commands” to begin configuring the commands. Click “Create Command” and configure it by simply giving it a name, a URL for the API endpoint and a few other details.
Joke Command
This is the /joke
command, it also supports an optional share
command which will share the joke with everyone in the current channel.
Weather Command
This is the /weather [zip code]
command, entering your zip code should yield a text representation of the weather as it corresponds to the given zip code.
Some people have noticed that I named the app “I’ve Got Jokes”, but it has a weather endpoint…it’s not really funny weather, it has more of a “dry” sense of humor - even on a rainy day.
Shorten URL Command
This is the /shortenUrl [long url]
command. Given a URL it will shorten the URL using bit.ly API. Perhaps you’re curious what long URL I shortened here, well this is
the bit.ly π€£
.
Conclusion
To recap we simply put together an ASP.NET Core Web API application that was published up to Azure as an App Service. The endpoints are configured as we integrate with the slack API, and ta-da…magic. All of this source code is up on GitHub . As always, feel free to star, fork and send me a pull request!
Share this post
Sponsor
Twitter
Facebook
Reddit
LinkedIn
StumbleUpon
Email