Sunday, April 21, 2019

Developing Containerized Entity Framework Core app on Mac using Docker hub



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.

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:
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”
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
“docker build -t mac-api .”

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

2.     Create image tag
Run “
docker tag mac-api [[YourDockerHubUserId]]/mac-api

3.     Push image to docker hub
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.