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:
- 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
dotnet --version
Now we need to install the entity framework tool
dotnet tool install --global dotnet-ef
Now we need to create an asp.net core application using the following command
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
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
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.
# 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 Core to utilise the port 80.
webBuilder.UseUrls("http://*:80");
Build the docker file and make sure it run
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
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
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
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.
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
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
DatabaseManagementService.MigrationInitialisation(app);
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);
}
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>
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>
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
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"
to build the docker file we need to use the following commands
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
Top comments (0)