Step by step guide on utilising Docker Compose with Asp.Net Core, SQL Server
Tags: dotnet, docker, beginners
Published: 2021-01-19 07:18:31

In this Article I will be showing you how to  how to create a Docker Compose file for an [ASP.Net](http://asp.Net) core application and SQL Server Database utilising the entity framework code first approach.

You can watch the full tutorial here:
{% youtube zrzdRINyYdM %}

Source code:
https://github.com/mohamadlawand087/v5-dockerComposeAspNetCpre

We will be creating a simple BookKeeping application where we will be able to add and view books and we will be utilising Docker Compose as will allow us to orchestrate multiple containers in a streamlined way, the 2 containers will be our SQLServer and [Asp.Net](http://asp.Net) Core application. 

The 4 things that we will need before we start:

- Visual Studio code (https://code.visualstudio.com)
- Dotnet core SDK (https://dotnet.microsoft.com/download)
- Docker download (https://www.docker.com/products/docker-desktop)
- Docker extension to VS code (https://code.visualstudio.com/docs/containers/overview))

After installing all of  requirements, we need to make sure that the dotnet SDK has been installed successfully, we need to open the terminal and check if the dotnet SDK is installed successfully by  checking the dotnet version

Open the terminal type the command below

```bash
dotnet --version
```

Now we need to install the entity framework tool

```bash
dotnet tool install --global dotnet-ef
```

Now we need to create an asp.net core application using the following command

```bash
dotnet new mvc -n "BookManagement" -lang "C#" -au none
```

Now lets open VS code and check the source code. than we need to open the terminal and add the required packages to utilise SQLServer and Entity Framework Core

```bash
dotnet add package Microsoft.EntityFrameworkCore --version 5.0.2
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 5.0.2
dotnet add package Microsoft.EntityFrameworkCore.Design --version 5.0.2
```

Once the packages are installed we can verify them in the csproj file. Now we need to build the project and run it to make sure everything is working as it should

```bash
dotnet build
dotnet run
```

As we can see the application is running, we need now to stop the application and add our Docker file. the Dockerfile will be added in the root directory of our application the D in Dockerfile must be capitalised. Once we add it we can see the whale icon.

```docker
# Get the base image
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env
WORKDIR /app

# Copy the csproj and restore all of the nugets
COPY *.csproj ./
RUN dotnet restore

# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/sdk:5.0
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "BookManagement.dll"]
```

As well lets update the program.cs class to force [Asp.Net](http://asp.Net) Core to utilise the port 80.

```csharp
webBuilder.UseUrls("http://*:80");
```

Build the docker file and make sure it run

```csharp
docker build -t bookmanagement .
docker run -p 8008:80 bookmanagement
```

Summarise what i did

Now once that is all set and done we need to move to our next step which is creating the model in the models folder, create a new class called book and add the following code

```csharp
public class Book
{
    public int Id { get; set; }
    public string Name { get; set; }
}
```

Next step is to create ApplicationDbContext ⇒ representation of our database in entity framework core

Create the Data folder, inside the data folder create the application DbContext calling it ApplicationDbContext

```bash
public class ApplicationDbContext : DbContext
{
    public DbSet<Book> Books {get;set;}
    
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
        
    }
}
```

We need to go our startup class and configure it, in the ConfigureService

```csharp
var server = Configuration["DbServer"] ?? "localhost";
var port = Configuration["DbPort"] ?? "1433"; // Default SQL Server port
var user = Configuration["DbUser"] ?? "SA"; // Warning do not use the SA account
var password = Configuration["Password"] ?? "Pa$$w0rd2021";
var database = Configuration["Database"] ?? "bookDb";

// Add Db context as a service to our application
services.AddDbContext<ApplicationDbContext>(options => 
    options.UseSqlServer($"Server={server}, {port};Initial Catalog={database};User ID={user};Password={password}"));
```

Now we need to add a database migration script in order to update the database once we connect to it. 

```bash
dotnet ef database migrations add "Initial Migration"
```

we need explain here why we are not going to run the migration directly

- there is not database yet as it will be initialised within docker compose
- we will back in the functionality into our startup class to check if there is migrations which is not implemented in the db to keep the database updated with the latest migration

Now we need to create our Database migration service, lets create a folder called services, inside that folder will create a class called DatabaseManagementService

```csharp
public static class DatabaseManagementService
{
     // Getting the scope of our database context
    public static void MigrationInitialisation(IApplicationBuilder app)
    {
        using (var serviceScope = app.ApplicationServices.CreateScope())
        {
             // Takes all of our migrations files and apply them against the database in case they are not implemented
            serviceScope.ServiceProvider.GetService<ApplicationDbContext>().Database.Migrate();
        }
    }
}
```

Now we need to call it from the startup class in the Configure method 

```csharp
DatabaseManagementService.MigrationInitialisation(app);
```

Now we need to update the main controller to pull information out of the database utilising the ApplicationDbContext 

```csharp
private readonly ILogger<HomeController> _logger;
private readonly ApplicationDbContext _context;

public HomeController(ILogger<HomeController> logger, ApplicationDbContext context)
{
    _logger = logger;
    _context = context;
}

public IActionResult Index()
{
    var books = _context.Books.ToList();
    return View(books);
}

[HttpGet]
public IActionResult Create()
{
    return View();
}

[HttpPost]
public async Task<IActionResult> Create(Book contact)
{
    // validate that our model meets the requirement
    if (ModelState.IsValid)
    {
        try
        {
            // update the ef core context in memory 
            _context.Books.Add(contact);

            // sync the changes of ef code in memory with the database
            await _context.SaveChangesAsync();

            return RedirectToAction("Index");
        }
        catch(Exception ex)
        {
            ModelState.AddModelError(string.Empty, $"Something went wrong {ex.Message}");
        }
    }

    ModelState.AddModelError(string.Empty, $"Something went wrong, invalid model");

    // We return the object back to view
    return View(contact);
}
```

Now we need to update the index view and build the create views. Index View we are going to have a very simple layout

```html
<a asp-action="create" asp-controller="Home">Create</a>

</a>
<div class="text-center">
 
 <ul>
    @foreach (var item in Model)
    {
        <li>@item.Name</li>
    }
 </ul>
</div>
```

Now lets add the create view, which is also very simple

```html
@model BookManagement.Models.Book

<form asp-action="Create" asp-area="Home" method="post">
    <div class="row">
        <div class="col-lg-12">
            <div class="form-group">
                <label> Name:</label>
                <input asp-for="Name" type="text" class="form-control required" placeholder="Enter name" tabindex="1">
            </div>
            <div class="form-group">
                <div asp-validation-summary="All" class="text-danger"></div>
            </div>
            <div class="card-footer">
                <center>
                    <button type="submit" class="btn btn-primary">Create Book</button>
                    <a asp-action="Index" asp-controller="Contacts" class="btn btn-secondary">Cancel</a>
                </center>
            </div>
        </div>
    </div>
</form>
```

Create a SQL Server instance using docker

```bash
docker run -d mcr.microsoft.com/mssql/server:2017-latest-ubuntu -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Youtube2021' -e 'MSSQL_PID=Express' -p 1433:1433 
```

Summarise what we did so far

try to build the docker image and see the output and it fails since by design 2 independent  containers cannot connect for this reason we need to use docker compose, which will allow to connect container with each other via networks

Now we create the docker compose file 

In the root directory of our project we add the docker-compose.yml file.

```yaml
version: '3'
services:
  ms-sql-server:
    image: mcr.microsoft.com/mssql/server:2017-latest-ubuntu
    environment:
      ACCEPT_EULA: "Y"
      SA_PASSWORD: "Pa55w0rd2021"
      MSSQL_PID: Express
    ports:
      - "1433:1433"
  book-app:
    build: .
    ports: 
      - "8090:80"
```

to build the docker file we need to use the following commands

```html
docker-compose up --build
```

Because we have defined both services in the docker compose file, they by default in the same network so the 2 containers will be able to communicate with each other