How to integrate with the Trello API from .NET/C#

I have been playing around with the Trello REST API recently. To my surprise, there doesn't seem to be any actively developed client package for .NET and Trello doesn't show examples in C# on their website. Here's a quick overview of what I have learned.

How to integrate with the Trello API from .NET/C#

For this post, I'll build a minimal console application in .NET 6 interacting with the Trello API. The code is not specific to console apps in any way and can be used on a website too if that's what you came here to achieve.

To communicate with the Trello REST API you will need an API key and a token. If you haven't already, sign up for a new user on trello.com. Next, go to https://trello.com/app-key and accept the developer terms. On the following page you will see your API key:

Developer API Keys

When developing a website you would normally want to let the user authorize your website's access to the Trello API using the authorize endpoint described in Trello's documentation. Since I'm developing a console application for this post, I'll use the Token link shown in the screenshot above. When generating a new token you will need to authorize access to your boards:

Authorize access

When clicking the Authorize button at the bottom of the page, a new token will be generated:

Token generated

With the API key and token in place, we are ready to start integrating. Create a new console application. For this demo, I'm using a .NET 6 minimal app:

dotnet new console

I'll also install all of the NuGet packages that we need to get everything wired up:

dotnet add package Microsoft.Extensions.Hosting
dotnet add package Microsoft.Extensions.Http
dotnet add package Spectre.Console
dotnet add package System.Net.Http.Json

The Microsoft.Extensions.Hosting package is needed to configure the host builder. Microsoft.Extensions.Http will serve as a way to configure the HttpClient needed to communicate with the API. Spectre.Console will generate some "UI" and System.Net.Http.Json contains a convenient way to map HTTP responses to strongly typed objects.

Would your users appreciate fewer errors?

➡️ Reduce errors by 90% with elmah.io error logging and uptime monitoring ⬅️

Let's start by configuring the dependencies:

var host = Host
    .CreateDefaultBuilder(args)
    .ConfigureServices((_, services) =>
    {
        services.AddHttpClient("trello", options =>
        {
            options.BaseAddress = new Uri("https://api.trello.com/1/");
            options.DefaultRequestHeaders.Authorization =
                new AuthenticationHeaderValue(
                    "OAuth",
                    "oauth_consumer_key=\"API_KEY\", oauth_token=\"TOKEN\"");
        });
    })
    .Build();

If you are familiar with ASP.NET Core, the code will be pretty straightforward. I use the builder to configure a new HttpClient. The base address is set to the root of Trello's REST API and the API key and token generated in the previous step is set as the authorization header on all requests. You will need to replace API_KEY and TOKEN with the values generated on your Trello account.

Next, I'll pull a new HttpClient:

var httpClientFactory = host.Services.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient("trello");

This will generate a new HttpClient with the base address and authorization headers configured in the previous step.

To show different ways and concepts of the API, I'll code up a small client able to pick a card and mark it as closed. Start by getting a list of boards:

var boards = await httpClient
    .GetFromJsonAsync<List<TrelloIdAndName>>($"members/me/boards");

The members/me/boards endpoint returns a list of Board objects from Trello. We are only interested in the Id and Name properties for this example, why I'm deserializing the response to a List of TrelloIdAndName objects:

internal class TrelloIdAndName
{
    public string Id { get; set; }
    public string Name { get; set; }

    public override string ToString()
    {
        return Name;
    }
}

With the list of boards returned from the API, I'll use Spectre.Console to generate a quick prompt:

var selectedBoard = AnsiConsole.Prompt(
    new SelectionPrompt<TrelloIdAndName>()
        .Title("Select board")
        .PageSize(10)
        .AddChoices(boards));
var selectedBoardId = selectedBoard.Id;

Let's run the code:

Prompt of boards

Success! I have three boards in that account. When picking one of the boards, I want to show a list of lists and let the user pick one of them. Finally, show the cards in the selected list a have the user pick one. The code looks like this:

var lists = await httpClient.GetFromJsonAsync<List<TrelloIdAndName>>($"boards/{selectedBoardId}/lists");

var selectedList = AnsiConsole.Prompt(
    new SelectionPrompt<TrelloIdAndName>()
        .Title("Select list")
        .PageSize(10)
        .AddChoices(lists));
var selectedListId = selectedList.Id;

var cards = await httpClient.GetFromJsonAsync<List<TrelloIdAndName>>($"lists/{selectedListId}/cards");

var selectedCard = AnsiConsole.Prompt(
    new SelectionPrompt<TrelloIdAndName>()
        .Title("Select card")
        .PageSize(10)
        .AddChoices(cards));
var selectedCardId = selectedCard.Id;

The code pretty much follows the same structure as when requesting the boards. I'm getting the lists inside the selected Trello boards. Then let the user pick on and output the cards in the selected list.

To get the details of the selected card, I'll generate a new model class:

internal class TrelloCard
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Desc { get; set; }
}

Like previously, I'm only mapping the properties I need for this sample. Each card contains a range of properties when returned from the Trello API.

Request the card details and output them in a table:

var card = await httpClient.GetFromJsonAsync<TrelloCard>($"cards/{selectedCardId}");

var table = new Table();
table.HideHeaders();
table.AddColumn("");
table.AddRow(card.Name);
table.AddRow(new Panel(card.Desc));

AnsiConsole.Write(table);

The code produces this UI in the console:

Card details

The final step is to let the user mark the card as closed:

if (AnsiConsole.Confirm("Close it?"))
{
    await httpClient.PutAsync($"cards/{card.Id}?closed=true", null);
}

If the user confirms the close, the PUT method on the card details is requested with closed=true. The entire session looks like this:

Trello client in action

That's it. With a few lines of code and a bit of magic from Spectre.Console we have made a simple Trello client. Here's the full code:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Spectre.Console;
using System.Net.Http.Headers;
using System.Net.Http.Json;

var host = Host
    .CreateDefaultBuilder(args)
    .ConfigureServices((_, services) =>
    {
        services.AddHttpClient("trello", options =>
        {
            options.BaseAddress = new Uri("https://api.trello.com/1/");
            options.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
                "OAuth",
                "oauth_consumer_key=\"API_KEY\", oauth_token=\"TOKEN\"");
        });
    })
    .Build();

var httpClientFactory = host.Services.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient("trello");

var boards = await httpClient.GetFromJsonAsync<List<TrelloIdAndName>>($"members/me/boards");

var selectedBoard = AnsiConsole.Prompt(
    new SelectionPrompt<TrelloIdAndName>()
        .Title("Select board")
        .PageSize(10)
        .AddChoices(boards));
var selectedBoardId = selectedBoard.Id;

var lists = await httpClient.GetFromJsonAsync<List<TrelloIdAndName>>($"boards/{selectedBoardId}/lists");

var selectedList = AnsiConsole.Prompt(
    new SelectionPrompt<TrelloIdAndName>()
        .Title("Select list")
        .PageSize(10)
        .AddChoices(lists));
var selectedListId = selectedList.Id;

var cards = await httpClient.GetFromJsonAsync<List<TrelloIdAndName>>($"lists/{selectedListId}/cards");

var selectedCard = AnsiConsole.Prompt(
    new SelectionPrompt<TrelloIdAndName>()
        .Title("Select card")
        .PageSize(10)
        .AddChoices(cards));
var selectedCardId = selectedCard.Id;

var card = await httpClient.GetFromJsonAsync<TrelloCard>($"cards/{selectedCardId}");

var table = new Table();
table.HideHeaders();
table.AddColumn("");
table.AddRow(card.Name);
table.AddRow(new Panel(card.Desc));

AnsiConsole.Write(table);

if (AnsiConsole.Confirm("Close it?"))
{
    await httpClient.PutAsync($"cards/{card.Id}?closed=true", null);
}

elmah.io: Error logging and Uptime Monitoring for your web apps

This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.

See how we can help you monitor your website for crashes Monitor your website