In this blog post, we will guide you through the process of creating a simple .NET 7 API that interacts with DynamoDB. Additionally, we will integrate our SlackBot console application, which we introduced in a previous blog post How to Create a Custom Slack Bot with .NET 7.
This is the second post in the series
💻 How to Create a Custom Slack Bot with .NET 7
🚀 Creating a Minimal API With ASP.NET Core That Interacts With AWS DynamoDB
🔧 Creating and Hosting An Application on AWS Elastic Container Service (ECS)
🚢 Continuous Deployment with GitHub Actions to AWS Elastic Container Service (ECS)
Before we dive into the details, it’s important to note that we could have created everything within the SlackBot console application. If you prefer that approach, feel free to do so. However, in this post, we are creating a separate Web API to allow for future integration with front-end applications, rather than solely relying on the SlackBot console application.
In this post, we are going to cover creating our ASP.NET Core API first. We will then add a ping endpoint to ensure our Web API is working as expected. Next, we will create an IAM role in the AWS Management Console, create a DynamoDB table in the AWS Management Console, add the AWSDynamoDB SDK and finally the code needed to call our DynamoDB table in AWS.
Don’t miss out on our upcoming posts! Subscribe now to stay in the loop and be the first to receive our latest content.
Creating an ASP.NET Core API
First, ensure that you have the .NET SDK installed as it is required to create our ASP.NET Core API using our command line interface. If you have not yet installed the SDK, head over to Microsoft’s download page.
Inside your command line interface (I’m using PowerShell), navigate to the directory where you want your API to be created. Then, type the following command:
dotnet new web -o SlackBot.WebAPI
Next, open your newly created project in your preferred IDE (I’m using Rider).
Minimal API
Minimal APIs are designed to build HTTP APIs with few dependencies. They are well-suited for microservices and applications that aim to have a lightweight structure by including only essential files, features, and dependencies in ASP.NET Core.
Now let’s add a ping endpoint to our Web API.
Adding a Ping Endpoint
The first endpoint we want to add is a ping endpoint. This is a simple endpoint that, when hit, will return an OK 200 status code. It is used to ensure that our API runs and is able to receive requests. This also helps demonstrate how we are going to set up our endpoints using the minimal style.
Inside our application, let’s examine the code in our Program.cs
file.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
As you can see, it’s very lightweight. Starting with .NET 6, Microsoft has removed the need to include a Startup.cs file and other boilerplate code inside the Program.cs file. The aim is to reduce any extra methods, lines of code, and extra braces (i.e., {}
) from the file to reduce the amount of unnecessary details.
We are going to delete line 4 (the example endpoint) and then add our own.
Inside our Program.cs file, after the builder.Build();
line, we are going to be using the TypedResults API. For more information on the standard Results vs. TypedResults, see “How to create responses in Minimal API apps“. In short, using TypedResults instead of Results offers several benefits, such as improved testability and automatic inclusion of response type information for OpenAPI documentation.
After the builder.Build();
line, add the following code to create a ping endpoint:
app.MapGet("/ping", Ping);
Then, after the app.Run();
line, add the following code:
static IResult Ping()
{
return TypedResults.Ok("Success");
}
This code adds a new endpoint at the /ping
route, which returns a response with the message “Ping successful” when it’s called.
Now, let’s run our API and test the ping endpoint. In your command-line tool, navigate to the project directory and run the following command:
dotnet run
Alternatively run the application in your IDE.
Once the API is running, open your browser or a tool like Postman and make a GET request to https://localhost:44335/ping
. You should receive a response with the message “Success” and a status code of 200.
With the ping endpoint set up, we can move on creating our DynamoDB table and IAM user.
Creating a DynamoDB Table in the AWS Management Console
To create a DynamoDB table in the AWS Management Console, follow these steps:
1. Create an AWS account if you don’t have one already. You can sign up for a free account here.
This page also explains about AWS free tier solutions. This means that for DynamoDB we get the following for free
2. Log into the AWS Management Console using your account credentials. The console URL is https://console.aws.amazon.com.
3. In the search box at the top of the page, type “DynamoDB” and select “DynamoDB” from the search results.
4. Click on “Create table” to create a new table.
5. Enter a table name, such as “ToDoList”, in the “Table name” field.
6. Enter a partition key in the “Partition key” field. For example, enter “Id”. In DynamoDB, the partition key value plays a crucial role by serving as input to an internal hash function. This function generates a specific output that determines the partition where an item will be stored within DynamoDB’s internal storage. Essentially, items sharing the same partition key value are grouped together and stored in a particular partition. Moreover, these items are arranged in a sorted order based on their respective sort key values.
7. Set any additional configuration options as needed.
8. Scroll down to the bottom of the page and click on “Create table” to create the DynamoDB table.
Once the table is created, you can use it to store and retrieve data.
Next, we want to create an IAM User to gain an Access and Secret Key that we will use to connect to our AWS account from our API.
Create an IAM User with an Access and Secret key
1. In the AWS Management Console, navigate to IAM and select “Users” from the left-hand menu.
2. Click on “Add users” and provide a suitable user name.
3. Specify user details according to your requirements.
4. On the “Set permissions” page, create a group. This will allow better management of user permissions by creating a group that we could then assign other people to. Give the group a meaningful name, such as “SlackBotDynamoDB”.
5. Search for “AmazonDynamoDbFullAccess” policy and attach it to the group.
6. Review the user details and click “Create User”.
7. Once the user is created, select it under “Users” in the main IAM page.
8. Go to the “Security Credentials” tab, click on “Create access key”, and follow the instructions to generate an access key and secret key. Remember to store them securely.
💡 AWS will give you suggestions on how to better manage access to AWS services. For now we are going to stick with our Access and Security Keys for testing purposes. Select Local code and ensure you check the checkbox saying you understand the recommendation and then next.
You will be presented with the following page ‘Retrieve access keys’ :
🚩 This will be the only time you can see your Secret Keys, so make sure you store them in a safe location.
Setting up AWS Credentials
To configure your AWS credentials to interact with your DynamoDB table, you can install the AWS CLI. If you haven’t installed it yet, refer to the AWS CLI Installation Guide.
To verify if you have the AWS CLI installed correctly, run the following command:
aws --version
Then run the following command to setup your credentials:
aws configure
You will be prompted to enter your access key and secret key, which you saved from the previous step. Additionally, provide the region where your DynamoDB table is located. For example, if your table is in the Sydney region (ap-southeast-2), enter “ap-southeast-2” as the default region. Leave the default output format empty and press enter to finish the AWS key setup. This process will create two files: a credentials file (containing access and secret key) and a config file (containing the specified region).You can find these files in the following location:
%userprofile%/.aws
Adding the AWS SDK and DynamoDB Code
To incorporate the necessary functionality for DynamoDB and enable dependency injection in our API project, we will add two packages: AWSSDK.DynamoDBv2
and AWSSDK.Extensions.NETCore.Setup
.
This can be done using the dotnet CLI. Open your command line interface and execute the following commands:
dotnet add package AWSSDK.DynamoDBv2
dotnet add package AWSSDK.Extensions.NETCore.Setup
These commands will install the required packages: AWSSDK.DynamoDBv2
provides the methods to interact with DynamoDB, and AWSSDK.Extensions.NETCore.Setup
allows for dependency injection of our AWS service client.
After successfully installing the AWSSDK.DynamoDBv2
package, we need to perform a few additional steps
Register IAmazonDynamoDb Interface and Load Credentials File
Returning to our SlackBot.WebAPI application, we need to register the GetAWSOptions
method from Amazon.Extensions.NETCore
and the IAmazonDynamoDB
interface from AWSSDK.DynamoDBv2
.
In the Program.cs
class, add the following code snippet after the WebApplication.CreateBuilder()
call but before the builder.Build()
:
builder.Configuration.GetAWSOptions();
builder.Services.AddAWSService<IAmazonDynamoDB>();
The GetAWSOptions
method retrieves the credentials and region from the credentials and config files we created earlier. The AddAWSService
registers the IAmazonDynamoDB
interface, enabling us to dependency injection the IAmazonDynamoDB
in other classes.
Our Program.cs class should look like the following.
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.GetAWSOptions();
builder.Services.AddAWSService<IAmazonDynamoDB>();
var app = builder.Build();
app.MapGet("/ping", Ping);
app.Run();
static IResult Ping()
{
return TypedResults.Ok("Success");
}
Add Items to DynamoDB
To add items to DynamoDB using the Object Persistence Model, follow these steps
💡 DynamoDB SDK for .NET offers three models that we can use. Low Level API, Document Model and Object Persistence Model. We are going to use the Object Persistence Model for our project. If you want to learn more about the different models have a look at the following AWS Site.
1. Create a new class named DynamoDbRepository in a folder named Repositories. In this class, initialize the DynamoDBContext and inject the IAmazonDynamoDB interface:
private readonly DynamoDBContext _context;
public DynamoDbRepository(IAmazonDynamoDB dynamoDbClient)
{
_context = new DynamoDBContext(dynamoDbClient);
}
2. Create a model class named ToDoRequest
with properties for Id
, Name
, IsComplete
, and Created
. Decorate the class with DynamoDB attributes:
[DynamoDBTable("ToDoList")]
public class ToDoRequest
{
[DynamoDBHashKey]
public string Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
public DateTime Created { get; set; }
}
3. Create a ToDoResponse
class with properties matching the response format:
public class ToDoResponse
{
public string Name { get; set; }
public bool IsComplete { get; set; }
public DateTime Created { get; set; }
}
4. Inside the DynamoDbRepository
class, add a method named AddItem
that takes a ToDoRequest
parameter and returns a Task<ToDoResponse>
:
public async Task<ToDoResponse> AddItem(ToDoRequest toDoRequest)
{
var uuid = Guid.NewGuid().ToString();
var nzDateTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.FindSystemTimeZoneById("New Zealand Standard Time"));
var request = new ToDoRequest
{
Id = uuid,
Name = toDoRequest.Name,
IsComplete = toDoRequest.IsComplete,
Created = nzDateTime
};
await _context.SaveAsync(request);
return new ToDoResponse
{
Name = request.Name,
IsComplete = request.IsComplete,
Created = request.Created
};
}
5. In the Program.cs
file, register the DynamoDbRepository
as a singleton:
builder.Services.AddSingleton<DynamoDbRepository>();
6. Add a route to access the AddItems
endpoint:
app.MapPost("/todoitems/", AddToDoItem);
7. Create the AddToDoItem
method that takes a ToDoRequest
and DynamoDbRepository
as parameters. Inside the method, call the AddItem
method of the DynamoDbRepository
:
static async Task<IResult> AddToDoItem([FromBody] ToDoRequest todo, DynamoDbRepository dynamoDbRepository)
{
var response = await dynamoDbRepository.AddItem(todo);
return TypedResults.Created($"/todoitems/{todo.Id}", response);
}
This method calls the AddItem
method and returns the response with a status of Created.
With these steps, you have added the functionality to add items to DynamoDB using the Object Persistence Model.
Our program.cs class looks like the following
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.GetAWSOptions();
builder.Services.AddAWSService<IAmazonDynamoDB>();
builder.Services.AddSingleton<DynamoDbRepository>();
var app = builder.Build();
app.MapGet("/ping", Ping);
app.MapPost("/todoitems/", AddToDoItem);
app.Run();
static IResult Ping()
{
return TypedResults.Ok("Success");
}
static async Task<IResult> AddToDoItem([FromBody] ToDoRequest todo, DynamoDbRepository dynamoDbRepository)
{
var response = await dynamoDbRepository.AddItem(todo);
return TypedResults.Created($"/todoitems/{todo.Id}", response);
}
Get Items from DynamoDB
To retrieve all items from DynamoDB, follow these steps:
1. In the DynamoDbRepository
class, add a method named GetAllItems
that returns a Task<IEnumerable<ToDoResponse>>
:
public async Task<IEnumerable<ToDoResponse>> GetAllItems()
{
var scanConfig = new ScanOperationConfig();
var searchResults = _context.ScanAsync<ToDoRequest>(scanConfig);
var response = await searchResults.GetNextSetAsync();
return response.Select(item => new ToDoResponse
{
Name = item.Name,
IsComplete = item.IsComplete,
Created = item.Created
});
}
This method uses the ScanAsync
method provided by the IAmazonDynamoDB
interface to perform a scan operation on the DynamoDB table. The response is then mapped to ToDoResponse
objects to include only the desired information.
2. In the Program.cs
file, add a route to access the “GetAllItems” endpoint:
app.MapGet("/todoitems", GetToDoItems);
3. Create the GetToDoItems
static method that takes DynamoDbRepository
as a parameter. Inside the method, call the GetAllItems
method of the DynamoDbRepository
:
static async Task<IResult> GetToDoItems(DynamoDbRepository dynamoDbRepository)
{
var response = await dynamoDbRepository.GetAllItems();
return Results.Ok(response);
}
This method calls the GetAllItems
method and returns the response with a status of OK (200).
With these steps, you have added the functionality to retrieve all items from DynamoDB using the Object Persistence Model.
Testing in Postman
To test the routes using Postman, follow these steps:
1. Run your application, and you will see two URLs printed in the application console window. These URLs correspond to the endpoints you have defined.
2. Open Postman and create a new request.
3. For testing the POST endpoint to add items, use the following details:
- Method: POST
- URL:
https://localhost:44335/todoitems
- In the body tab, select “Raw” and set the body format to JSON.
- Add the request properties inside a
ToDoRequest
object, for example:
{
"Name": "Finish AWS Blog",
"IsComplete": "false"
}
- Send the request and observe the response, which should include a ToDoResponse object with the provided name, completion status, and created date.
4. For testing the GET endpoint to retrieve all items, use the following details:
- Method: GET
- URL:
https://localhost:44335/todoitems
- Send the request and observe the response, which should include an array of
ToDoResponse
objects representing the items stored in the DynamoDB table.
By testing these endpoints in Postman, you can verify that the routes are functioning correctly and interacting with the DynamoDB table as expected.
Connecting our SlackBot console application to the SlackBot API
The next stage is to create an http request from our SlackBot console application to call off to our SlackBot API project. To allow us to add and get our To Do items.
To hook up your SlackBot console application to your SlackBot API, follow these steps:
1. Install the Microsoft.Extensions.Http
package by running the following command:
dotnet add Microsoft.Extensions.Http
This is going to allow us to register Microsoft’s AddHttpClient shown in step 5.
2. Create a new class named SlackBotApiService
with the following code:
public class SlackBotApiService
{
private readonly HttpClient _httpClient;
public SlackBotApiService(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("https://localhost:44335/");
}
public async Task<IEnumerable<ToDoResponse?>> GetToDoItems() => await _httpClient.GetFromJsonAsync<IEnumerable<ToDoResponse>>("todoitems");
}
We have implemented a Typed Client in our solution. If you want to delve deeper into HTTP requests and explore the different styles you can use in ASP.NET Core, I recommend consulting the comprehensive documentation provided by Microsoft. The documentation covers various approaches to making HTTP requests, including the usage of the HttpClient class, the HttpRequestMessage class, and the IHttpClientFactory. It provides detailed information and valuable insights to enhance your understanding. You can access the documentation by visiting HTTP requests in ASP.NET Core.
3. Create a class named ToDoResponse
to represent the response model from the API:
public class ToDoResponse
{
public string? Name { get; set; }
public bool IsComplete { get; set; }
public DateTime Created { get; set; }
}
This is the same response model we used in our SlackBot.WebApi project.
4. Create a class named GetToDoItemsHandler
that implements the IEventHandler<MessageEvent>
interface. This handler will handle the “get todo items” command from Slack and retrieve the to-do items from the API:
public class GetToDoItemsHandler : IEventHandler<MessageEvent>
{
private const string Trigger = "get todo items";
private readonly ISlackApiClient _slack;
private readonly SlackBotApiService _slackBotApiService;
public GetToDoItemsHandler(ISlackApiClient slack, SlackBotApiService slackBotApiService)
{
_slack = slack;
_slackBotApiService = slackBotApiService;
}
public async Task Handle(MessageEvent slackEvent)
{
if (slackEvent.Text?.Equals(Trigger, StringComparison.OrdinalIgnoreCase) == true)
{
var items = await _slackBotApiService.GetToDoItems();
var members = new StringBuilder();
foreach (var item in items)
{
members.AppendLine($":red_circle: {item.Name}");
members.AppendLine($":white_check_mark: Completed: {item.IsComplete}");
members.AppendLine($":date: Created: {item.Created}");
members.AppendLine();
}
await _slack.Chat.PostMessage(new Message {
Text = members.ToString(),
Channel = slackEvent.Channel });
}
}
}
In this class, there are a few important components at play. First, we set the trigger, which is the specific phrase the bot listens for in Slack. When the trigger phrase “get todo items” is detected, the rest of the code in this class will execute.
We utilize dependency injection to inject the HttpClient and SlackBotApiService into our class. This allows us to make HTTP requests to our SlackBot.WebApi.
Within the Handle
method, we make a call to our SlackBot.WebApi using the SlackBotApiService. Once we receive the response from the API, we use the StringBuilder
method to iterate through the results and format them into separate lines. To enhance the appearance in Slack, we include emojis such as :red_circle:
to make the content visually appealing.
Finally, we post the formatted message to the channel in Slack, providing the user with the requested to-do items in an organized manner.
5. Add the following code to your Program.cs
class below the var services = new ServiceCollection();
call:
services.AddHttpClient<SlackBotApiService>();
6. Register the GetToDoItemsHandler
handler in your Program.cs
class:
.RegisterEventHandler<MessageEvent, GetToDoItemsHandler>()
Now that we have the logic to get our to do items, let’s now add a way to add items.
Create a new class named AddToDoItemHandler
that implements the IEventHandler<MessageEvent>
interface. This handler will handle the “add todo” command from Slack and add a new to-do item to the API:
public class AddToDoItemHandler : IEventHandler<MessageEvent>
{
private const string Trigger = "add todo";
private readonly ISlackApiClient _slack;
private readonly SlackBotApiService _slackBotApiService;
public AddToDoItemHandler(ISlackApiClient slack, SlackBotApiService slackBotApiService)
{
_slack = slack;
_slackBotApiService = slackBotApiService;
}
public async Task Handle(MessageEvent slackEvent)
{
if (slackEvent.Text?.Contains(Trigger, StringComparison.OrdinalIgnoreCase) == true)
{
var toDoItemName = slackEvent.Text.Split(',').Skip(1).FirstOrDefault();
var isComplete = Convert.ToBoolean(slackEvent.Text.Split(',').Skip(2).FirstOrDefault());
var request = new ToDoRequest { Name = toDoItemName, IsComplete = isComplete };
await _slackBotApiService.PostToDoItems(request);
await _slack.Chat.PostMessage(new Message {
Text = $"{toDoItemName} has been added to your to do list",
Channel = slackEvent.Channel });
}
}
}
7. Add the following code to the SlackBotApiService class to handle the POST request for adding a to-do item:
public async Task PostToDoItems(ToDoRequest todoItem)
{
var todoItemJson = new StringContent(JsonSerializer.Serialize(todoItem), Encoding.UTF8, MediaTypeNames.Application.Json);
await _httpClient.PostAsync("todoitems", todoItemJson);
}
}
8. Create a class named ToDoRequest to represent the request model for adding a to-do item:
public class ToDoRequest
{
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
9. Register the AddToDoItemHandler handler in your Program.cs class:
.RegisterEventHandler<MessageEvent, AddToDoItemHandler>()
10. In your Program.cs
class, add the following code to configure the SlackBotApiService
as an HttpClient service:
services.AddHttpClient<SlackBotApiService>();
With these changes, your SlackBot console application will be able to interact with the SlackBot API to add and retrieve to-do items from Slack.
Testing In Slack
Now that we have all the code written. We can now run both applications to check how it works.
Ensure you have both the SlackBot.WebAPI and SlackBot console application running.
Inside our Slack Workspace that we created in our previous blog. We can type the following commands
get todo items
Provided that you have items in your DynamoDb database, you should get a response back with those items.
We can also add items, but saying
add todo, Testing Adding Item, False
💡 I have used the comma “,” to allow me to separate out the words, first the name of my item and second if the task has been completed. You might want to look into a better way to handle this, but for now, this is what we will go with.
Once you have your item added, try using get todo list again and you should see your new to do item.
If you liked this post make sure to subscribe
Final Thoughts
There are numerous ways to enhance and ensure the robustness of our SlackBot. For instance, instead of relying on text input for adding items, we can employ a model to input To-Do items. This approach would provide clarity and eliminate the need for commas to separate each property. Additionally, we can further expand the code to enable task completion, display only completed or non-completed tasks, and implement stronger null exception handling.
This blog has provided a concise overview of building an ASP.NET Core API that interacts with Slack and AWS DynamoDB.
Armed with the knowledge gained here, you are now equipped to develop your own powerful SlackBot applications tailored to your specific requirements. Happy coding!
[…] 🚀 Creating a minimal API with ASP.NET Core that interacts with AWS DynamoDB […]
LikeLike
[…] 🚀 Creating a minimal API with ASP.NET Core that interacts with AWS DynamoDB […]
LikeLike
[…] 🚀 Creating a Minimal API With ASP.NET Core That Interacts With AWS DynamoDB […]
LikeLike