Deploy a .NET API to Heroku through GitHub Actions
Tags: dotnet, heroku, github, devops
Published: 2021-11-28 18:39:51

In this article we are going to learn how to deploy a .NET API to Heroku through GitHub Actions. Also, we are going to create a PostgreSQL database in Heroku.

## Prerequisites:
* Visual Studio 2022 with .NET 6 SDK
* pgAdmin and PostgreSQL Database
* GitHub account
* Heroku account

# 1. Create .NET project
Create a .NET Web API project, named `NETToDoDemo`. Select `.NET 6` framework, authentication `None`, disable Configure for HTTPS, enable Docker, and select `Windows` as Docker OS.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kfsoaivm390b0o0qeynt.png)
 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/03wsya4w9d89uqkghkxn.png)
 
Remove WeatherForecast class and WeatherForecastController controller.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5zyy7zw5esak657s1jlk.png)

Right click to the project / Edit Project file. Then, disable nullable property.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/41yt4m16kii9u65htrby.png)
 
Create `Entities` folder and inside this create `ToDo` class.

```csharp
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace NETToDoDemo.Entities
{
    public class ToDo
    {
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        [MaxLength(30)]
        public string Name { get; set; }
        public string? Description { get; set; }

        [Range(1, 3)]
        public int Priority { get; set; }
        public bool IsCompleted { get; set; }
    }
}
```

Install the following packages:
* Microsoft.EntityFrameworkCore.Design
* Microsoft.EntityFrameworkCore.Tools
* Npgsql.EntityFrameworkCore.PostgreSQL

Create `Contexts` folder, and inside this create `ToDoContext` class.

```csharp
using Microsoft.EntityFrameworkCore;
using NETToDoDemo.Entities;

namespace NETToDoDemo.Contexts
{
    public class ToDoContext : DbContext
    {
        public ToDoContext(DbContextOptions<ToDoContext> options) : base(options)
        {
        }

        public DbSet<ToDo> ToDos { get; set; }
    }
}
```

Open pgAdmin and create `ToDoDemo` database.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gpmjez1bp4vot9hxmaf2.png)

Open appsettings.Development.json file and add the connection string.

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Database=ToDoDemo;Username=postgres;Password=12345678"
  }
}
```

In Program.cs file we are going to get the connection string and configure the Db Context.

```csharp
using Microsoft.EntityFrameworkCore;
using NETToDoDemo.Contexts;

var builder = WebApplication.CreateBuilder(args);
var configurationBuilder = new ConfigurationBuilder()
                            .SetBasePath(builder.Environment.ContentRootPath)
                            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                            .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true)
                            .AddEnvironmentVariables();

builder.Configuration.AddConfiguration(configurationBuilder.Build());

// Add services to the container.

var defaultConnectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ToDoContext>(options =>
   options.UseNpgsql(defaultConnectionString));

...
```

Open `Package Manager Console`. And execute the following commands:
* Add-Migration InitialCreate -Context StoreContext
* Update-Database

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9y3fitir7w59d0q2fz8r.png)
 
Go to pgAdmin, ToDoDemo database and expand Schemas / public / Tables. As you can see there are 2 tables

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d403bvd8sj4edr66gipd.png)

In .NET project, right click to Controllers / Add / Controller ..., then select API, API Controller with actions, using Entity Framework.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/buqbbz8xq91y97f9r2mq.png)

Use the below configuration and click on Add.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ph242cuw34azber0e1k4.png)

Make sure it's selected `IIS Express` as profile. Then, go to Debug tab / Start Without Debugging.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dpc4bi1b8seopedac0yk.png)
 
All the methods from ToDosController are available to test.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yqrglkd9cwc5hzzm0t9v.png)

Let's test all the methods. First, create a ToDo.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wkfz8ypc6v62kxkio2px.png)

As you can see, the ToDo was created in the database.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5k6k2g21lomibyl3jdl4.png)

Then, we are going to update it. Go to the PUT method. Don't forget to specify the id in the parameters.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b9eun8bm78gz16utwuh2.png)
  
Test GET ToDos and GET ToDos by id.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0g9fnw5wn9tt2j4mzmdf.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qfw7r71m5517jy011rtc.png)

Finally, test the DELETE method.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/myubkkeo4fghtrg7bfvx.png)

And if you test GET /api/ToDos again, the list should be empty.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lwf1j9hvqc55cnli5hiv.png)  

# 2. Create Heroku project and PostgreSQL database
Go to [Heroku](https://www.heroku.com/) and create an account if you haven't one. Click in New / Create new app.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6kq0m8mjgkq7vk0oieg7.png)
 
Type a name for the app and then `Create app`.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ks0zsnj645pxwvpj78wl.png)
 
Go to `Resources` tab and click in `Find more add-ons`.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pxi5kgurznxuspix92ma.png)
 
In the search box, type `postgres` and hit Enter.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wq707z2ve7srftp1wtwk.png)
 
Select Heroku Postgres and click on Install Heroku Postgres.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fx9yo6q2vggkxi9ouh21.png)
 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0xqnkag7infe0i79y5lt.png)

Select the app you created and click on Submit Order Form.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f7qesggvzaxsnp8b0iug.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1pczscooi76ylbd3djs2.png)

Click in `Heroku Postgres`.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d50xnp3zdbd3xhi75caq.png)
  
Go to the Settings tab and click in view credentials.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c46ifz82f2okzdcezzrt.png)

The same credentials you can see in the Settings tab from the app panel.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xq6q5p9ug4k6ul0rdkup.png) 

Add `ASPNETCORE_ENVIRONMENT` config var.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tjstxhjqd5jb6y7xhpb2.png)

In .NET project, in Program.cs, add the following below `builder.Services.AddDbContext`.

```csharp
var serviceProvider = builder.Services.BuildServiceProvider();
try
{
    var dbContext = serviceProvider.GetRequiredService<ToDoContext>();
    dbContext.Database.Migrate();
}
catch
{
}
```

Also, add the following above `builder.Services.AddDbContext`.

```csharp
var defaultConnectionString = string.Empty;

if (builder.Environment.EnvironmentName == "Development") {
    defaultConnectionString = builder.Configuration.GetConnectionString("DefaultConnection");
}
else
{
    // Use connection string provided at runtime by Heroku.
    var connectionUrl = Environment.GetEnvironmentVariable("DATABASE_URL");

    connectionUrl = connectionUrl.Replace("postgres://", string.Empty);
    var userPassSide = connectionUrl.Split("@")[0];
    var hostSide = connectionUrl.Split("@")[1];

    var user = userPassSide.Split(":")[0];
    var password = userPassSide.Split(":")[1];
    var host = hostSide.Split("/")[0];
    var database = hostSide.Split("/")[1].Split("?")[0];

    defaultConnectionString = $"Host={host};Database={database};Username={user};Password={password};SSL Mode=Require;Trust Server Certificate=true";
}
```

Move Dockerfile to the solution directory and update it. Make sure it's as the following structure.

```docker
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["NETToDoDemo/NETToDoDemo.csproj", "NETToDoDemo/"]
RUN dotnet restore "NETToDoDemo/NETToDoDemo.csproj"
COPY . .
WORKDIR "/src/NETToDoDemo"
RUN dotnet build "NETToDoDemo.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "NETToDoDemo.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
CMD ASPNETCORE_URLS=http://*:$PORT dotnet NETToDoDemo.dll
```

From Heroku, click in your avatar image / Account settings.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lzqmux9blgmfsdm2oaip.png) 

Go to API Key, click in reveal. Copy the API Key, we are going to use it in the next section.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k20oj34tbyjbgrtozid1.png)  

# 3. Create repository in GitHub and implement CI/CD workflow
Go to your GitHub account, create a repository and push the project. Then, click on the Settings tab.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oulwhbfthai20xzmpr7l.png)
 
Go to `Secrets` tab and click in `New repository secret`.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xu9ptj90fv9ylzcgwsdr.png)
 
Add the following secrets:
* Your Heroku API KEY
* Your Heroku email
* Your Heroku App Name

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hrsjvlwyzot6cxtvnbgs.png)

Click on `Actions` tab.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z4ey3t20mjk2wpgsmtaz.png)

Click in `set up a workflow yourself`.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1cll5o0qtf9jpz5zs7mo.png)
   
Replace the main.yml file with the following

```yml
name: Deploy to Heroku

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: akhileshns/heroku-deploy@v3.12.12
        with:
          heroku_api_key: ${{secrets.HEROKU_API_KEY}}
          heroku_app_name: ${{secrets.HEROKU_APP_NAME}}
          heroku_email: ${{secrets.HEROKU_EMAIL}}
          usedocker: true
```

This file is responsible to deploy automatically to Heroku using the secrets we created and the Dockerfile in the .NET project.

Click in `Start commit` and then `Commit new file`.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u6nedw0e5vf7bj6l4jcw.png)
 
We have to wait until the GitHub action finishes. As you can see it was successful.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r5uw6jkndo17aejmgl35.png)
 
If you go to the `Overview` tab in your Heroku app. You can notice it was added something in `Dyno information`.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mmx1rvguzg7dmfbqpmi7.png)
 
You can test your API with the following base URL `https://<APP_NAME>.herokuapp.com`.

You can find the source code [here](https://github.com/cristofima/NETToDoDemo).

**Thanks for reading**

Thank you very much for reading, I hope you found this article interesting and may be useful in the future. If you have any questions or ideas that you need to discuss, it will be a pleasure to be able to collaborate and exchange knowledge together.