Introduction
In my last blogpost I walked through the process of simple
API app using dotnet core on Mac using Visual Studio Code and Docker hub. In
this blog post, I am going to take that app further and enhance it with Entity Framework
Core to provide database support. Before we move on to create such app, I
wanted to talk about why it makes sense to have an app on Mac using docker hub.
There are few advantages that such architecture offers. One is that is going to
be a Linux based which means that we can deploy it on most of the cloud
infrastructures and plus it is going to cost less. A typical Linux instance
with comparable memory and processing speeds costs less than a windows instance.
The other advantage is that since we are making this a container, we can use
this on any container orchestration platforms such as Kubernetes or Apache Mesos. In summary developing this application we get
the following benefits:
1.
Containerization
2.
Linux based
3.
Developed on Mac (Although you can develop this on Linux
machine as well)
4.
Database persistence using a light weight database
5.
Leveraging ASPNet Core and EF Core technologies.
A public repository has been created which contains all the
code for this blog post. The code used in this blog post can be found at: https://github.com/mnabeel/mac-api
Prerequisites
Visual Studio Code
.NET Core SDK
Docker
SQLite
Postman for testing (Optional)
Before starting off on implementing the solution, let us
take a look at what we are building.
Goal:
Create a containerized API app that allows adding of Member
information in database. The API app will allow also to update and delete
member information from the database.
Plan
I have divided the development of this application in five
parts.
1.
Create Entity Framework Core app using Visual Studio
code
2.
Create Dockerfile that will support the application
3.
Create docker image and push to docker hub
4.
Create docker container by pulling docker image from
docker hub
5.
Test the application
You can download the code from the github repository which
will allow you to skip directly to Part 2.
Part 1: Create Entity Framework Core app using Visual Studio code
Step 1: Creating simple API app
Note: If you are continuing on the app that was created in
the previous blog post then you can skip Step 1. Also, you can download the
code directly from github repository at: https://github.com/mnabeel/mac-api
Creating application folder
Open
terminal
Run
following commands
“mkdir
mac-api”
“cd
mac-api”
Create API application
On
the same terminal where application folder was created, run the following two commands:
1.
“dotnet new webapi”: This will create a new dotnet core app on
your application folder (mac-api). Here is the output of running this command:
The template "ASP.NET Core Web API" was created successfully.
The template "ASP.NET Core Web API" was created successfully.
Processing
post-creation actions...
Running
'dotnet restore' on /Users/XXXX/aspnetcore/mac-api/mac-api.csproj...
Restoring packages for /Users/XXXX/aspnetcore/mac-api/mac-api.csproj...
Generating MSBuild file
/Users/muhammadnabeel/aspnetcore/mac-api/obj/mac-api.csproj.nuget.g.props.
Generating MSBuild file /Users/XXXXXX/aspnetcore/mac-api/obj/mac-api.csproj.nuget.g.targets.
Restore completed in 2.22 sec for /Users/XXXX/aspnetcore/mac-api/mac-api.csproj.
Restore succeeded.
2.
“dotnet restore”: This command will restore the dependencies and
tools of a project. Here is the output of this command:
Restore completed in 58.86 ms for
/Users/muhammadnabeel/aspnetcore/mac-api/mac-api.csproj.
Editing the application
Once application has
been created, switch to Visual Studio Code to edit the project.
1.
Open Visual Studio Code.
2.
Open the application folder “mac-api”
3.
Modify Startup.cs to remove code related to HTTPS redirecting.
After the modification your Startup.cs code should look like the following:
4.
using Microsoft.AspNetCore.Builder;
5.
using Microsoft.AspNetCore.Hosting;
6.
using Microsoft.AspNetCore.Mvc;
7.
using Microsoft.Extensions.Configuration;
8.
using Microsoft.Extensions.DependencyInjection;
9.
10.
namespace mac_api
11.
{
12.
public class Startup
13.
{
14.
public Startup(IConfiguration configuration)
15.
{
16.
Configuration = configuration;
17.
}
18.
19.
public IConfiguration Configuration { get; }
20.
21.
// This method gets
called by the runtime. Use this method to add services to the container.
22.
public void ConfigureServices(IServiceCollection services)
23.
{
24.
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
25.
}
26.
27.
// This method gets
called by the runtime. Use this method to configure the HTTP request pipeline.
28.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
29.
{
30.
if (env.IsDevelopment())
31.
{
32.
app.UseDeveloperExceptionPage();
33.
}
34.
app.UseMvc();
35.
}
36.
}
37.
}
You can run the current app to verify that it runs and
changes to Startup.cs were correct. Before running the app, we need to make
sure that port that we are going to use is not used by any other application.
For that we will take a look at file called “launchSettings.json” which is
present in the “Properties” folder as shown below:
I already had port 5001 and 5000 being used by another
application so I changed the ports on line 24 to be ports 7001 and 7000 as
shown above.
Run the following command from your terminal:
“dotnet run”
Here is the output of the above command:
“dotnet run”
Here is the output of the above command:
Using
launch settings from /Users/XXXX/aspnetcore/mac-api/Properties/launchSettings.json...
info:
Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
User profile is available. Using
'/Users/muhammadnabeel/.aspnet/DataProtection-Keys' as key repository; keys
will not be encrypted at rest.
Hosting
environment: Development
Content
root path: /Users/XXXX/aspnetcore/mac-api
Now
listening on: https://localhost:7001
Now
listening on: http://localhost:7000
Application
started. Press Ctrl+C to shut down.
Now we can open the browser and browse the http://localhost:7000/api/values
and see the following output:
We have created our application using dotnet core sdk on a
Mac. We can now move on to the next step of adding Entity Framework Core
support and creating the MembersController.
Step 2: Creating MembersController with EF Core support that allows CRUD
functions
For this step, we are going to take following actions:
Adding support for NuGet Package Manager
In order for us to use Entity
Framework Core, we need to ensure NuGet Package Manager is installed on Visual
Studio Code. Install the NuGet Package Manager by clicking the “Extensions”
button on the left panel of Visual Studio Code and enter “Package Manger” in
the search bar. This will list the NuGet Package Manager that you can install
as shown below:
Adding support for Entity Framework Core
Once the NuGet Package Manager is installed,
you can use the command palette as shown below:
Following two packages should be installed
for this step.
1. Microsoft.EntityFrameworkCore
2.
Microsoft.EntityFrameworkCore.Sqlite
Note: Since our Mac-Api app is using
dotnetcore version 2.1, we need to make sure that Entity Framework Core matches
the version. In our case both packages had the compatible version of 2.1.1.
Once both packages are added the mac-api.csproj
file would look like below:
Add Model
Create a folder calls models in Visual Studio Code
and add a file called Member.cs as shown below:
Add EFCore Context
Create a folder called ‘Data’. This folder
will house our MemberContext.cs file that will provide the connection between
our Members API controller and database. Add the MemberContext.cs to Data
folder with code as shown below:
Adding SQLite database file
For our application we are going to use a lightweight
database called SQLite. This will be the database that we will use in our
container as well. In order to create the database file (MacApiMembers.db), we
will switch to terminal and completing the following steps:
a.
First, we verify that we have SQLite installed. On your
terminal window run the following command:
“sqlite3 –version”
“sqlite3 –version”
The output of this command will be like shown below:
b.
Create MacApiMembers.db
To create the database file run the following command:
“sqlite3 MacApiMembers.db”
Just running this command will not result in the MacApiMembers.db file.
We also need to create table. When we run the “sqlite3 MacApiMembers.db”, we
are presented with sqlite prompt. That is where we will execute the following command to create
Member table as shown below:
“create table Member(Id smallint, Name varchar(50));
This command will be entered in the sqlite> prompt.
Once the above command is run, the MacApiMember.db file is added as shown
in the following screenshot of the Visual Studio Code:
Adding code for MembersController.cs
Up till now we have ensured that all the dependencies that
we need for MembersController.cs are in place before we code the MembersController.cs.
Now we can go ahead and jump right in. Add a new file to the “Controllers” folder
and call it “MembersController.cs” file. This will contain our code that will carry
out the goal of executing the CRUD operations. Here is the code for the “MembersController.cs”.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using MacApi.Models;
using MacApi.Data;
namespace MacApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MembersController : ControllerBase
{
private readonly MemberContext membersContext;
public MembersController(MemberContext context) {
membersContext = context;
}
public List<Member> Members = new List<Member>();
// GET api/members
[HttpGet]
public ActionResult<IEnumerable<Member>> Get() {
var members = membersContext.Member.ToList();
// Members.Add(new Member() {
//
Id = 1,
//
Name = "MemberName"
// });
return members;
}
// GET api/members/5
[HttpGet("{id}")]
public ActionResult<Member> Get(int id)
{
var member = membersContext.Member.FirstOrDefault(m => m.Id == id);
return member;
}
// POST api/members
[HttpPost]
public void Post([FromBody] Member member)
{
membersContext.Member.Add(member);
membersContext.SaveChanges();
}
// PUT api/members/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
var member = membersContext.Member.FirstOrDefault(m => m.Id == id);
if(member == null) {
throw new Exception("Provided member id not
found");
}
member.Name = value;
membersContext.SaveChanges();
}
// DELETE api/members/5
[HttpDelete("{id}")]
public void Delete(int id)
{
var member = membersContext.Member.FirstOrDefault(m => m.Id == id);
if(member == null) {
throw new Exception("Provided member id not
found");
}
membersContext.Member.Remove(member);
membersContext.SaveChanges();
}
}
}
The code is pretty self-explanatory. We are just using HTTP
verbs to add, update and delete member information.
Once the coding part is done, you can go ahead and run the
application to make sure everything runs okay.
Since we are using Visual Studio code, I found it very
helpful to just use the debugger that comes with Visual Studio Code. The tool
that I use to test any API application is Postman.
Here is the screenshot that shows executing the get action to
display all the members:
Here is the screen shot that shows how a new member information
is added using the post method:
This concludes the end of part 1. We can now move to next
part.
Part 2: Create Dockerfile that will support the application
Create a “Dockerfile”. If you are continuing from previous post,
then the “Dockerfile” should already be there. In that case ensure that it
shows the following content:
FROM
microsoft/dotnet:2.1-sdk as build
WORKDIR /app
# Copy csproj and
restore as distinct layer
COPY *.csproj ./
RUN dotnet restore
# Copy SQLite
database file
COPY *.db ./
# Copy everything
else and build
COPY . ./
RUN dotnet publish -c
Release -o out
# Build runtime
image
FROM
microsoft/dotnet:2.1-aspnetcore-runtime as runtime
WORKDIR /app
COPY --from=build /app/out
.
# Install SQLite
RUN apt-get -y update
RUN apt-get -y upgrade
RUN apt-get install -y sqlite3 libsqlite3-dev
RUN sqlite3
MacApiMembers.db "create table
Member (Id smallint, Name varchar(50));"
ENTRYPOINT ["dotnet", "mac-api.dll"]
Let us talk a little about the “Dockerfile” mentioned above. Most of the code written above for “Dockerfie”
is pretty straight forward, we are copying the contents of our application in
build and then creating a runtime image. In the runtime image, there is a section
that is related to SQLite. Since we are embedding the database with the
application, it is important to add SQLite command in the “Dockerfile” so when
the container is created, it would have all the dependencies. There is one command
in particular that I want to draw your attention to. The command is mentioned
like:
RUN sqlite3
MacApiMembers.db "create table
Member (Id smallint, Name varchar(50));"
The above command is creating the table in the MacApiMembers.db.
This is part of the setup for the application. This command is added so that we
can make sure that table is created before the application is launched. This
way the application will run without making adjustments to the tables.
Part 3: Create docker image and push to docker hub
In the previous part we created the “Dockerfile” that is necessary
to create the container. In this part we will create the docker image and push
it to docker hub.
In
order to build docker image for our “mac-api” application, run the following
command
Again,
make sure mac-api is replaced with the application name that is being used.
Running the above command will take some time as it will download all the dependencies. Once done, following output is received:
To
verify that you have created a docker image for your app, run the following
command:
“docker images”
Running the above command yields the following output:
Now
we have the docker image that we can push to docker hub. This is a three-step
process
1. Login
Run “docker login” command on your terminal
Run “docker login” command on your terminal
2. Create image tag
Run “docker tag mac-api [[YourDockerHubUserId]]/mac-api”
Run “docker tag mac-api [[YourDockerHubUserId]]/mac-api”
3. Push image to docker hub
Run “docker push [[YourDockerHubUserId]]/mac-api”
Run “docker push [[YourDockerHubUserId]]/mac-api”
This step concludes the part 3. In the next part we will
create the docker container by pulling the docker image that we just pushed to docker
hub.
Part 4: Create docker container by pulling docker image from docker hub
On
terminal, type the following command:
“docker run --name mac-api-remote --env
ASPNETCORE_ENVIRONMENT=Development -p 80:80 [[YourDockerHubId]]/mac-api:latest”
What is happening in this command? We are using the run command that is going to create a
docker container. For the name, we have given mac-api-remote just to indicate
that the image for this container is being pulled from docker hub, but any name
can be used here. -p is telling us what port we are going to bind the container
with. The last is the address to our docker image.
When we
run the above command, we get the following output instructing us to browse the
site using http://localhost/api/values.
In order to see the members, we will navigate to http://localhost/api/members.
To
thoroughly test the application, we will need to use Postman. In the next and
final part, we will talk about using Postman for carrying out the testing.
Part 5: Testing
If you
look at the MembersController.cs, you can see we have five actions that are for:
1.
Getting all the members(HttpGet)
2.
Getting a particular
member (HttpGet),
3.
Posting a new member
(HttpPost)
4.
Updating a
particular member (HttpPut)
5.
Deleting a particular
member (HttpDelete).
Let us test these one by one using Postman.
Testing getting all members:
Testing getting record for a particular member
Testing adding a new member
Testing updating a particular member
Testing deleting a particular member
Let call the get all members again to see what has been updated
and deleted. Following screenshot shows the result:
As you can see from the above result, we were able to update
the member 3 with name field as “ThirdMember” and member 2 is not present because
it was deleted.
That concludes the final part of testing the application.
Conclusion
In this blog post, we have created and tested a
containerized dotnet core app that using Entity Framework Core to manipulate a SQLite
database. This blog post has shown that in order to develop a robust dotnet
core app, we can use a Mac with results as good as developing similar
application on windows PC. This also shows that using free tools, like Visual
Studio Code, Postman and docker we can create production grade application with
no cost on development tools. With this post, I hope users will be able to use
dotnet core apps more and leverage the free tools that are available. Please provide
any comments or feedback. Your feedback will definitely help this post and next
posts in future.