Step by step guide on utilising Docker Compose with Asp.Net Core, SQL Server

moe23

Mohamad Lawand

Posted on January 19, 2021

Step by step guide on utilising Docker Compose with Asp.Net Core, SQL Server

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

You can watch the full tutorial here:

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 Core application.

The 4 things that we will need before we start:

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

dotnet --version
Enter fullscreen mode Exit fullscreen mode

Now we need to install the entity framework tool

dotnet tool install --global dotnet-ef
Enter fullscreen mode Exit fullscreen mode

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

dotnet new mvc -n "BookManagement" -lang "C#" -au none
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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

dotnet build
dotnet run
Enter fullscreen mode Exit fullscreen mode

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.

# 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"]
Enter fullscreen mode Exit fullscreen mode

As well lets update the program.cs class to force Asp.Net Core to utilise the port 80.

webBuilder.UseUrls("http://*:80");
Enter fullscreen mode Exit fullscreen mode

Build the docker file and make sure it run

docker build -t bookmanagement .
docker run -p 8008:80 bookmanagement
Enter fullscreen mode Exit fullscreen mode

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

public class Book
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

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

public class ApplicationDbContext : DbContext
{
    public DbSet<Book> Books {get;set;}

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {

    }
}
Enter fullscreen mode Exit fullscreen mode

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

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}"));
Enter fullscreen mode Exit fullscreen mode

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

dotnet ef database migrations add "Initial Migration"
Enter fullscreen mode Exit fullscreen mode

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

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();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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

DatabaseManagementService.MigrationInitialisation(app);
Enter fullscreen mode Exit fullscreen mode

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

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);
}
Enter fullscreen mode Exit fullscreen mode

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

<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>
Enter fullscreen mode Exit fullscreen mode

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

@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>
Enter fullscreen mode Exit fullscreen mode

Create a SQL Server instance using docker

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 
Enter fullscreen mode Exit fullscreen mode

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.

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"
Enter fullscreen mode Exit fullscreen mode

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

docker-compose up --build
Enter fullscreen mode Exit fullscreen mode

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

💖 💪 🙅 🚩
moe23
Mohamad Lawand

Posted on January 19, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related