Click here to Skip to main content
14,121,894 members
Click here to Skip to main content
Add your own
alternative version

Stats

25.2K views
52 bookmarked
Posted 24 Aug 2018
Licenced CPOL

Running ASP.NET Web API Solution in Docker

, 6 Mar 2019
Rate this:
Please Sign up or sign in to vote.
This article describes how to containerize an ASP.NET WebApi solution with Visual Studio, how to pass data via environment variables and also how to run the images in docker without Visual Studio.

Introduction

The tricky part when running a web solution with a web API in docker containers is to map the URLs and ports so that the code running inside the docker container can be accessed from outside. This is a question of docker configuration and minor code changes.

Background

This article is a contribution to the Docker Contest described in this article.

Prerequisites

  • Visual Studio 2017, community version. Latest release.
  • You have installed “Docker For Windows” on your computer: https://download.docker.com/win/stable/Docker for Windows Installer.exe.
  • You have an existing solution with a web API and a web “model-view-controller“ project and the MVC project is able to communicate with the web API via a REST http interface. If not, you may use the CarApi and CarClient projects (see below) to implement your own solution.

The code belonging to this article is the containerized versions of CarClient and CarApi from this article.

In this article, docker support has been added and the docker configuration files have been updated to make it possible to access the API from CarClient, both frontend and backend.

How to Containerize an Existing Project

To add docker support for an existing web project, e.g., CarApi, open the project in Visual Studio, right click the project and chose Add -> Docker Support:

1257705/Containerize.jpg

A docker configuration file, “Dockerfile”, is created and it looks like this:

# For more info see: http://aka.ms/VSContainerToolingDockerfiles
FROM microsoft/aspnetcore:2.0 AS base
WORKDIR /app
EXPOSE 80

FROM microsoft/aspnetcore-build:2.0 AS builder
WORKDIR /src
COPY *.sln ./
COPY CarApi/CarApi.csproj CarApi/
RUN dotnet restore
COPY . .
WORKDIR /src/CarApi
RUN dotnet build -c Release -o /app

FROM builder AS publish
RUN dotnet publish -c Release -o /app

FROM base AS production
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "CarApi.dll"

Do this for both projects in your existing solution, i.e., for the web API and the web MVC project. When this is done, you need to add a docker-compose project to your solution.

Add a docker-compose Project

To add a docker-compose project to the solution, right click one of the projects and select Add -> Container Orchestrator Support -> Docker Compose -> Target OS:Linux.

1257705/Docker-compose.jpg

The added project is of type “.dcproj” and the following files are created:

1257705/Docker-composeFiles.jpg

The next step is to right click the other project and in the same way, select Add -> Container Orchestrator Support ->Docker Compose -> Target OS:Linux.

Suppose that your two projects are called “CarClient” and “CarApi”, then the resulting docker-compose.yml looks like this:

version: '3.4'

services:
  web:
    image: ${DOCKER_REGISTRY}carclient
    build:
      context: .
      dockerfile: CarClient/Dockerfile

  api:
    image: ${DOCKER_REGISTRY}carapi
    build:
      context: .
      dockerfile: CarApi/Dockerfile

The Containerized Solution with docker-compose

After having added Dockerfiles to each project and the docker-compose project to the solution, the solution consists of three projects: A web MVC project, a web API project and a docker-compose project.

Add Environment Variable

To make the containerized version function, we need to make some configuration changes.

Backend

In the original CarClient project, the web API was reached via the following URL:

private static readonly Uri Endpoint = new Uri("http://localhost:54411/");

Here, the URL is hard coded but we could also define an environment variable in launchSettings.json:

"profiles": {
  "IIS Express": {
    "commandName": "IISExpress",
    "launchBrowser": true,
    "environmentVariables": {
      "ASPNETCORE_ENVIRONMENT": "Development",
      "CarApiUrl": "http//localhost:54411/"
    }

The environment variable is read like this:

var carApiUrl = Environment.GetEnvironmentVariable("CarApiUrl");

For the containerized solution, we use “dns discovery”. Docker networking, as well as kubernetes handles all this magic. Instead of localhost, the name of the service, as defined in the docker-compose, is used. To call the CarApi, use http://carapi. You don’t need to set the port number as the port number is an external attribute.

We will use the environment variable called CarApiUrl. This variable is defined in the docker-compose.yml file like this:

version: '3.4'

services:
...
  carclient:
    image: ${DOCKER_REGISTRY}carclient
    environment:
      - CarApiUrl=http://carapi/
    build:
      context: .
      dockerfile: CarClient/Dockerfile

The environment variable is read in file Utils.cs:

private static readonly Uri Endpoint = new Uri(Environment.GetEnvironmentVariable("CarApiUrl"));

By using an environment variable, we don't need to change the code, only the configuration, when containerizing the solution.

Frontend

The JavaScript running in the browser uses port 54411. We must expose port 54411 by changing the docker configuration file for CarApi like this:

In the web API Dockerfile, write EXPOSE 54411:

# For more info see: http://aka.ms/VSContainerToolingDockerfiles
FROM microsoft/aspnetcore:2.0 AS base
WORKDIR /app
EXPOSE 54411
...

In the docker-compose.yml, map external port 54411 to internal 80:

version: '3.4'

services:
  carapi:
    image: ${DOCKER_REGISTRY}carapi
    ports:
      - 54411:80
...

The original JavaScript code is kept:

xmlhttp.open("GET", "http://localhost:54411/api/car", true);

That’s all that's needed. You can now run your containerized solution in Visual Studio.

Run Your App without Visual Studio

Rebuild your solution with Visual Studio with the Release configuration. Run the docker-compose project with F5 to ensure the images are updated.

Outside of Visual Studio, you’ll need to use the docker-compose command rather than docker run. In PowerShell, cd to the solution folder where docker-compose.yml is located. Then run the docker-compose command like this:

…> docker-compose --no-ansi up -d  --force-recreate --remove-orphans

Then check with docker ps on which port carclient is running:

…> docker ps

CONTAINER ID    IMAGE     COMMAND                    CREATED          STATUS         PORTS   …

5c5c3a6fa376     carclient  "dotnet CarClient.dll"   12 hours ago  Up 12 hours  0.0.0.0:32781->80/tcp

20cf31344091    carapi       "dotnet CarApi.dll"       12 hours ago   Up 12 hours  54411/tcp, ….

Carclient runs on port 32781. The app will then become accessible at http://localhost:32781.

Explanation

If the Debug configuration is set, then empty non-workable images are created by Visual Studio. It manually maps the empty container to the filesystem to make possible debugging, "Edit and Continue" features and so on. Therefore, dev image is useless without Visual Studio. Build the image in the Release configuration to make it usable.

The full publishing process is described in the documentation: Visual Studio Tools for Docker.

Run Your App with Docker Networking

It’s possible to make containers communicate via docker networking without YAML.

First some useful docker commands:

Kill all, (start, run, rm all)

>>docker kill $(docker ps -aq)      

Start shell inside container

>>docker exec -i -t container_name /bin/bash

Run the solution without docker-compose but with docker networking

cd to carapi

>>docker build -t carapi .

>>docker run -e ASPNETCORE_ENVIRONMENT=Development -d -p 54411:80 --name carapi carapi

Inspect the docker bridge network to find the IP address used by carapi;

>> docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "fce049eb23e5fb1a7b5c801a082d8809efd4d369f18de4693b35e6524f1d55c0",
        "Created": "2019-03-04T15:13:40.1528498Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "cbf2ae63374659b4a9c8e341e22b20a7f3e6d7b6593d289d7ffa4db415d6e8b6": {
                "Name": "carapi",
                "EndpointID": "0c25cdaa8403c928b12efc1e5f6bb40b71e76acc438f8b95d2ba7a135eb333e9",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            },
            "f1abe1fdb72a23b61f7160aa49aca06f9c849dd3fca9432257168175625589a2": {
                "Name": "carclient",
                "EndpointID": "aa4dd99b8045e5a0c77f6dadd17ad9d3579c82c3cf7ee2af45461e6284523739",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

You see that carapi is using IP: 172.17.0.3. Use this ip number for the environment variable CarApiUrl in the docker run command below:

cd to carclient

>>docker build -t carclient .

>>docker run -e ASPNETCORE_ENVIRONMENT=Development 
  -e CarApiUrl=http://172.17.0.3 -d -p 8080:80 --name carclient carclient

Then start client in a browser with localhost:8080.

Now the containers communicate without YAML using the docker bridge network.

Points of Interest

In this article, we've discussed how to containerize an ASP.NET WebApi solution with Visual Studio, how to pass data via environment variables and also how to run the images in docker without Visual Studio. We also see how to use docker networking as an alternative to docker-compose and YAML files.

History

  • August 24, 2018 - The first revision of this article was published
  • September 1, 2018 - Environment variable was introduced
  • September 6, 2018 - Run the app without Visual Studio
  • March 6, 2019 - Run app with docker networking without docker-compose

The code is available here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Gunnar S
Software Developer (Senior)
Sweden Sweden
I work as Senior Developer mainly in Microsoft environment and my strenghts are SQL, C# and ReactJS.

You may also be interested in...

Pro

Comments and Discussions

 
PraiseWell Done Pin
KennedyKinyanjui26-Mar-19 22:04
memberKennedyKinyanjui26-Mar-19 22:04 
QuestionKestrel Pin
Irene Troupansky8-Mar-19 6:58
memberIrene Troupansky8-Mar-19 6:58 
AnswerRe: Kestrel Pin
Member 1345924029-Apr-19 5:57
memberMember 1345924029-Apr-19 5:57 
QuestionHow does this work (THE ANSWER) Pin
Sacha Barber30-Aug-18 0:37
mvaSacha Barber30-Aug-18 0:37 
AnswerRe: How does this work (THE ANSWER) Pin
Gunnar S30-Aug-18 19:27
memberGunnar S30-Aug-18 19:27 
GeneralRe: How does this work (THE ANSWER) Pin
Sacha Barber30-Aug-18 19:54
mvaSacha Barber30-Aug-18 19:54 
QuestionHow does this work? Pin
Sacha Barber30-Aug-18 0:32
mvaSacha Barber30-Aug-18 0:32 
AnswerRe: How does this work? Pin
Gunnar S30-Aug-18 19:33
memberGunnar S30-Aug-18 19:33 
Hello Sacha.
Good that you are observant. In the article I mix the usage of api and carapi. In the code I use only carapi. I will update the article accordingly.

Best regards, Gunnar
Questiongreat job Pin
Member 1002253027-Aug-18 11:00
memberMember 1002253027-Aug-18 11:00 
AnswerRe: great job Pin
Gunnar S27-Aug-18 22:50
memberGunnar S27-Aug-18 22:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web06 | 2.8.190518.1 | Last Updated 7 Mar 2019
Article Copyright 2018 by Gunnar S
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid