C# (C Sharp) CRUD Rest API using .NET 7, ASP.NET, Entity Framework, Postgres, Docker and Docker Compose
Tags: dotnet, webdev, beginners, docker
Published: 2023-04-16 08:06:08

Let's create a CRUD Rest API in C# (or C sharp), using:
- .NET 7 
- ASP.NET (Framework for building web apps)
- Entity Framework (ORM)
- Postgres (Database)
- Docker (Containerization)
- Docker Compose (To run the database and the application)

Video version:

{% youtube PY6uD1fgzZA %}

All the code is available in the GitHub repository (link in the video description): https://youtube.com/live/PY6uD1fgzZA

___
## 🏁 Intro

Here is a schema of the architecture of the application we are going to create:

[![crud, read, update, delete, to a ASP.NET (C# logo) and Postgres service, connected with Docker compose. Postman and Tableplus to test it](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7vw1fm4zu41856gga5bw.png)](https://youtube.com/live/PY6uD1fgzZA)

We will create 5 endpoints for basic CRUD operations:
- Create
- Read all
- Read one
- Update
- Delete

Here are the steps we are going through:

0. Define the requirements
1. Create a new C# project 
2. Install the dependencies
3. Configure the database connection
4. Create the logic for the CRUD operations
5. Update the project 
6. Dockerfile
7. docker-compose.yml
8. Run the Postgres database and the application
9. Update the database schema
10. Test the application

We will go with a step-by-step guide, so you can follow along.

___
## 📋 Requirements:

- .NET 7 installed and running
- dotnet CLI
- (Optional): C# Extension Pack for VS Code
___
## 🚀 Create a new C# project

There are many ways to create a new C# project, but I will use the dotnet CLI

Open a terminal and run the following command:

```bash
dotnet new webapi -n csharp-crud-api
```

Now step into the directory:

```bash
cd csharp-crud-api
```

___
## 📦 Install the dependencies

We will use the following dependencies:
- Entity Framework
- Npgsql (Postgres driver)

To install them, be sure to be in the ``csharp-crud-api`` directory and run the following commands:

```bash
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
```

### 🔍 Test the base application
Before we proceed, let's see if the project is configured correctly.

Open the folder in VS Code (or your favorite IDE).
```
code .
```

You should see the following structure:

[![Structure of the C# project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pxn0e8b1y0y8zz51vkpd.png)](https://youtube.com/live/PY6uD1fgzZA)

Now run the following command to start the application:

```bash
dotnet run --urls=http://localhost:5000
```

You should see the following output:

[![Output of the dotnet run command](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vi1adytkfv6tbjevy6re.png)](https://youtube.com/live/PY6uD1fgzZA)

To test the application, open a browser and go to the following URL: ``http://localhost:5000/weatherforecast``

You should see the following output:

[![Get response with weather](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r8tpgae8exyhscum968n.png)](https://youtube.com/live/PY6uD1fgzZA)

Now it's time to start coding the application.
___
## 📝 Code the application

There are three steps to code the application:
- Configure the database connection
- Create the logic for the CRUD operations
- Update the ``Program.cs`` file

### 🗄️ Configure the database connection

open the ``appsettings.json`` file and add the following lines:

```json
{
  "ConnectionStrings": {
    "DefaultConnection": "Host=db;Port=5432;Database=postgres;Username=postgres;Password=postgres"
  }
}
```

Your ``appsettings.json`` file should look like this:

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Host=db;Port=5432;Database=postgres;Username=postgres;Password=postgres"
  }
}
```


### ✏️ Create the logic for the CRUD operations

Create a new folder called ``Models`` and a new file called ``User.cs`` inside it.

[![Structure of the C# project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pxn0e8b1y0y8zz51vkpd.png)](https://youtube.com/live/PY6uD1fgzZA)

Populate ``User.cs`` with the following code:

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

namespace Models
{
    [Table("users")]
    public class User
    {
        [Column("id")]
        public int Id { get; set; }

        [Column("name")]
        public string Name { get; set; }

        [Column("email")]
        public string Email { get; set; }
    }
}
```

Now create a new folder called ``Data`` and a new file called ``UserContext.cs`` inside it.

Populate the ``UserContext.cs`` file with the following code:

```csharp
using Models;
using Microsoft.EntityFrameworkCore;

namespace Data
{
    public class UserContext : DbContext
    {
        public UserContext(DbContextOptions<UserContext> options) : base(options)
        {
        }

        public DbSet<User> Users { get; set; }
    }
}
```

Now create a new folder called ``Controllers`` and a new file called ``UsersController.cs`` inside it.


Populate the ``UserscController.cs`` file with the following code:

```csharp
using Data;
using Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace csharp_crud_api.Controllers;


[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
  private readonly UserContext _context;

  public UsersController(UserContext context)
  {
    _context = context;
  }

  // GET: api/users
  [HttpGet]
  public async Task<ActionResult<IEnumerable<User>>> GetUsers()
  {
    return await _context.Users.ToListAsync();
  }

  // GET: api/users/5
  [HttpGet("{id}")]
  public async Task<ActionResult<User>> GetUser(int id)
  {
    var user = await _context.Users.FindAsync(id);

    if (user == null)
    {
      return NotFound();
    }

    return user;
  }

  // POST: api/users
  [HttpPost]
  public async Task<ActionResult<User>> PostUser(User user)
  {
    _context.Users.Add(user);
    await _context.SaveChangesAsync();

    return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
  }

  // PUT: api/users/5
  [HttpPut("{id}")]
  public async Task<IActionResult> PutUser(int id, User user)
  {
    if (id != user.Id)
    {
      return BadRequest();
    }

    _context.Entry(user).State = EntityState.Modified;

    try
    {
      await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
      if (!UserExists(id))
      {
        return NotFound();
      }
      else
      {
        throw;
      }
    }

    return NoContent();
  }

  // DELETE: api/users/5
  [HttpDelete("{id}")]
  public async Task<IActionResult> DeleteUser(int id)
  {
    var user = await _context.Users.FindAsync(id);
    if (user == null)
    {
      return NotFound();
    }

    _context.Users.Remove(user);
    await _context.SaveChangesAsync();

    return NoContent();
  }

  private bool UserExists(int id)
  {
    return _context.Users.Any(e => e.Id == id);
  }

  // dummy method to test the connection
  [HttpGet("hello")]
  public string Test()
  {
    return "Hello World!";
  }
}
```

### 🖋️ Update the ``Program.cs`` file

We are almost done, now we just need to update the ``Program.cs`` file.

add these 2 imports at the top of the file:

```csharp
using Data;
using Microsoft.EntityFrameworkCore;
```

And these lines lines above the ``var app = builder.Build();`` line:

```csharp
// Added configuration for PostgreSQL
var configuration = builder.Configuration;
builder.Services.AddDbContext<UserContext>(options =>
    options.UseNpgsql(configuration.GetConnectionString("DefaultConnection")));
```

Or you can replace the whole ``Program.cs`` file with the following code:

```csharp
using Data;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Added configuration for PostgreSQL
var configuration = builder.Configuration;
builder.Services.AddDbContext<UserContext>(options =>
    options.UseNpgsql(configuration.GetConnectionString("DefaultConnection")));

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
```

We are done with the application logic, now it's time to Dockerize it.

___
## 🐳 Dockerize the application

We will Dockerize the application in creating and populating two files:
- Dockerfile
- docker-compose.yml

### 🐋 Dockerfile

At the root of the project create a new file called ``Dockerfile`` and populate it with the following code:

```dockerfile
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /app

COPY *.csproj ./
RUN dotnet restore

COPY . ./
RUN dotnet publish -c Release -o out

FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS runtime
WORKDIR /app
COPY --from=build /app/out ./

ENTRYPOINT ["dotnet", "csharp-crud-api.dll"]
```

Explanation:

We have 2 stages in the Dockerfile, `build` and `runtime`.

Build stage:
- ``FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build``: This line tells Docker to use the ``mcr.microsoft.com/dotnet/sdk:7.0`` image as the base image for the build stage.
- ``WORKDIR /app``: This line tells Docker to set the working directory to ``/app``.
- ``COPY *.csproj ./``: This line tells Docker to copy all the ``.csproj`` files to the working directory.
- ``RUN dotnet restore``: This line tells Docker to run the ``dotnet restore`` command.
- ``COPY . ./``: This line tells Docker to copy all the files to the working directory.
- ``RUN dotnet publish -c Release -o out``: This line tells Docker to run the ``dotnet publish`` command.

Runtime stage:
This stage is very similar to the build stage, the only difference is that we are using the ``mcr.microsoft.com/dotnet/aspnet:7.0`` image as the base image.
- ``FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS runtime``: This line tells Docker to use the ``mcr.microsoft.com/dotnet/aspnet:7.0`` image as the base image for the runtime stage.
- ``WORKDIR /app``: This line tells Docker to set the working directory to ``/app``.
- ``COPY --from=build /app/out ./``: This line tells Docker to copy the ``out`` folder from the build stage to the working directory.
- ``ENTRYPOINT ["dotnet", "csharp-crud-api.dll"]``: This line tells Docker to run the ``dotnet csharp-crud-api.dll`` command.


### 🐙 docker-compose.yml

At the root of the project create a new file called ``docker-compose.yml`` and populate it with the following code:

```yaml
version: '3'
services:
  csharp_app:
    container_name: csharp_app
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:80"
    depends_on:
      - db
    environment:
      ConnectionStrings__DefaultConnection: "Host=db;Database=postgres;Username=postgres;Password=postgres"
  db:
    container_name: db
    image: postgres:12
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=postgres
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata: {}
```

Explanation:

Explanation:
- We define 2 services: ``csharp_app`` and ``db``.
Csharp_app:
- ``container_name: csharp_app``: This line tells Docker to name the container ``csharp_app``.
- ``build:``: This line tells Docker to build the image from the ``Dockerfile``.
- ``ports:``: This line tells Docker to expose the port ``8080`` of the container to the port ``80`` of the host.
- ``depends_on:``: This line tells Docker that the ``csharp_app`` container depends on the ``db`` container.
- ``environment:``: This line tells Docker to set the environment variables for ``ConnectionStrings__DefaultConnection``.

Db:
- ``container_name: db``: This line tells Docker to name the container ``db``.
- ``image: postgres:12``: This line tells Docker to use the ``postgres:12`` image as the base image for the container.
- ``environment:``: This line tells Docker to set the environment variables for ``POSTGRES_USER``, ``POSTGRES_PASSWORD`` and ``POSTGRES_DB``.
- ``ports:``: This line tells Docker to expose the port ``5432`` of the container to the port ``5432`` of the host.
- ``volumes:``: This line tells Docker to mount the ``pgdata`` volume to the ``/var/lib/postgresql/data`` directory of the container.

We also need to define the ``pgdata`` volume, used by the ``db`` container.

We are done. Now we can run the Postgres and the app containers.

___
## 🏃‍♂️ Run the application

We will run the containers (services) defined in the ``docker-compose.yml`` file.

### 🐘 Run the Postgres container

```
docker compose up -d db
```

### 📜 Create the table in the database

We can create the table in different ways, but let me show you how you can do it directly from the app container.

First, be sure that the Postgres container is running.

Open a **different** terminal and run the following command:

```bash
docker exec -it db psql -U postgres
```

And we are inside of the Postgres Container now. We can create the table with the following command:

```sql
CREATE TABLE "users" (
  "id" SERIAL PRIMARY KEY,
  "name" VARCHAR(50) NOT NULL,
  "email" VARCHAR(255) NOT NULL
);
```

### 🏗️ Build and run the app container

Build the Docker image:

```bash
docker compose build
```

Run the app container:

```bash
docker compose up csharp_app
```

___
## 🧪 Test the application

Let's test the application with Postman

Now we can test the project. We will use Postman, but you can use any other tool.

### 📝 Create a user

To create a new user, make a `POST request to localhost:8080/api/users`.

The body of the request should be like that:

```json
{
    "name": "aaa",
    "email": "aaa@mail"
}
```

The output should be something like that:

[![create user](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9njm0g5ss18gn28ri0h1.png)](https://www.youtube.com/live/BbT1PCAOS2s)


Let's create two more users, make a `POST request to localhost:8080/api/users`.

```json
{
    "name": "bbb",
    "email": "bbb@mail"
}
```


```json
{
    "name": "ccc",
    "email": "ccc@mail"
}
```

### 📝 Get all users

To get all users, make a ``GET request to localhost:8000/api/users``.

The output should be something like that:

[![get all users](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4wkn21ed67fxuhvf1s5q.png)](https://www.youtube.com/live/BbT1PCAOS2s)


### 📝 Get a user

To get a user, make a ``GET request to localhost:8000/api/users/{id}``.

For example ``GET request to localhost:8000/api/users/1``.

The output should be something like that:

[![get a user](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o3nfw5yqigq1xwmmizqi.png)](https://www.youtube.com/live/BbT1PCAOS2s)

### 📝 Update a user

To update a user, make a ``PUT request to localhost:8000/api/users/{id}``.

For example ``PUT request to localhost:8000/api/users/2``.

The body of the request should be like that:

```json
{
    "name": "Francesco",
    "email": "francesco@mail"
}
```

The output should be something like that:

[![update a user](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tw4qlrqx6jmnc9leb20k.png)](https://www.youtube.com/live/BbT1PCAOS2s)


### 📝 Delete a user

To delete a user, make a ``DELETE request to localhost:8000/api/users/{id}``.

For example ``DELETE request to localhost:8000/api/users/1``.

On Postman you should see something like that:

[![delete a user](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/libog6q806cf4prl90dd.png)](https://www.youtube.com/live/BbT1PCAOS2s)


### Final test

As a final test, we can check the database using TablePlus.

[![tableplus](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d8rjxjmbitn0y05dohcr.png)](https://www.youtube.com/live/BbT1PCAOS2s)


___
## 🏁Conclusion

We made it! we built a CRUD Rest API project in C# (or C sharp), using:
- .NET 7 
- ASP.NET (Framework for building web apps)
- Entity Framework (ORM)
- Postgres (Database)
- Docker (Containerization)
- Docker Compose (To run the database and the application)

Video version:

{% youtube PY6uD1fgzZA %}

All the code is available in the GitHub repository (link in the video description): https://youtube.com/live/PY6uD1fgzZA


That's all. 

If you have any question, drop a comment below.

[Francesco](https://francescociulla.com)