Practical ASP.NET
Docker with Real Applications
Here's how to run a "real-world" application (consisting of an MVC application and a Web Service) in a networked set of Docker containers. And it's just a couple of mouse clicks in Visual Studio to implement it.
In this column I'm going to show you how to use Visual Studio to create Docker containers for the ASP.NET Core projects in your solution. I'm going to assume something like a modern application solution: an ASP.NET MVC Core project that depends upon several class libraries plus a Web Service called from the application that's also part of the solution.
To do all of that, while I'm going to be using Docker and Docker Compose, most of the work will be taken care of by Visual Studio. And when I'm done, when I want to start debugging, I just press F5. To make it much more funner, though, along the way I'll explain what's going on.
Installing Docker Desktop for Windows
Before you start, you'll need to download Docker Desktop and install it (I've used Docker Desktop 2.0.3 for this article). When installing, do not take the option to use Windows containers. Once the installation is done, open the Windows Start menu, type "Docker Desktop" and then press the <Enter> key to start Docker Desktop running.
Once you've got Docker up and running, right-click on the Docker icon in the Windows taskbar notification area and select Settings from the pop-up menu. When the Settings dialog displays, go to the Shared Drives section, check off drive C and click the Apply button (you may have to provide your password). Your choice on whether to take the option to start Docker every time you log on (I did). Close the Settings dialog when you're done.
I found I had to reboot my computer at this point (among other benefits, this ensures that both Visual Studio and PowerShell know all about Docker). After restarting, and before rushing into anything, you should check the Docker icon in the notification area to ensure that it's no longer in an "animated" mode. Once the icon stops cycling, you know that Docker is ready to be used.
Setting Up Your ASP.NET MVC Core Project
Setting up your project to run in a container is easy. For my ASP.NET Core MVC project, I just right-click on the project and select Add | Container Orchestrator Support. That will pop up a dialog that asks if you want to use Service Fabric or Docker Compose. Service Fabric will only work with Windows containers, and since .NET Core runs on everything, I'd pick Docker Compose.
That will lead you to a second dialog that will ask if you want to use Windows or Linux containers. So far, my experience has been that (a) everything I want to do with .NET Core runs on Linux, and (b) the documentation is a lot more extensive for Linux containers. Based on that, I'd pick Linux if I were you.
You'll now discover that you have a new project in your solution called docker-compose and that it's been set as your Solution's startup project. You can tell it's the startup project because, in Solution Explorer, the project is in boldface and the start debugging button on Visual Studio's toolbar says "docker-compose." In the docker-compose project is a file called docker-compose.yml. If you double-click it open you'll find that it contains something like this:
services:
MyMVCProjectName:
image: ${DOCKER_REGISTRY-}netcoresdk20
build:
context: .
dockerfile: MyMVCProjectName/Dockerfile
In this example, MyMVCProjectName is the name of the container that will be created when you press F5. The image line references the image that your project will be based on. Turning that Docker image into a running container consists of executing the instructions in the Dockerfile file referenced in the dockerfile line.
Adding and Accessing a Web Service
The next step is to include the solution's Web Service project when I start debugging. I could add the project to the container with my MVC project. However, deploying the Web Service in its own container positions me to manage the service in production separately from my application.
To implement that decision, I just right-click on my Web Service project and go through the same process as I did with my application: Select Add | Container Orchestrator Support, select Docker Compose and select Linux. When I'm done my Web Service project will have a Dockerfile of its own. More importantly, the docker-compose.yml file in the docker-compose project will have been updated. It might look something like this now:
services:
MyMVCProjectName:
image: ${DOCKER_REGISTRY-}netcoresdk20
build:
context: .
dockerfile: MyMVCProjectName/Dockerfile
MyWebServiceProjectName:
image: ${DOCKER_REGISTRY-}netcoresdk20
build:
context: .
dockerfile: MyWebServiceProjectName/Dockerfile
When you press F5 to start debugging, Docker checks to see if the referenced images are already on your computer. If not, Docker fetches the image from a Docker repository. Docker Compose will then cause the containers to be created from the information in the Dockerfiles, start both containers and provide, effectively, an internal network connecting the containers. Each of the containers will appear as a kind of server on that network and, internally, I can use the container names in place of server names.
For example, before I "containerized" my MVC application I was accessing the Web Service from my MVC application with a URL like this:
http://localhost:1867/api/CustomerServices/Customer/A123
In the container environment I must replace the server name in the URL with the container name for the service from my docker-compose.yml file. For my example, that URL would look like this:
http://MyWebServiceProjectName/api/CustomerServices/Customer/A123
After making those changes, when I press F5, Visual Studio will work much the way it does without containers: The Web Browser I selected in the project will start up, it will have two tabs open, and one of those tabs will be displaying my application's start page. The other tab will have been opened as part of starting the Web Service.
Four Warnings
You're free to modify the Dockerfile or the docker-compose.yml file. For example, you might want to add a depends_on parameter to your application's description in docker-compose.yml to ensure that your application isn't displayed to you until the container holding the service is, at least, loaded. That change would look like this:
MyMVCProjectName:
image: ${DOCKER_REGISTRY-}netcoresdk20
build:
context: .
dockerfile: MyMVCProjectName/Dockerfile
depends_on:
- MyWebServiceProjectName
First warning: The dialect that these files are written (YAML) is very particular about indenting (Python developers will, I'm told, be used to this). In the previous example, you'd need to make sure that:
- The depends_on line lines up with the build line above it
- The hyphen beginning the following line lines up with the dockerfile line above it
Second warning: The docker-compose.yml file is not automatically saved when you press F5. To see the effect of any changes you've made, you need to save the file before starting debugging.
Third warning: If your application won't start when you press F5, then it's probably a problem with the docker-compose.yml file. The easiest way I've discovered to get a useful error message is to do a rebuild. Any message that appears after that and refers to "block end," "block start" or "mapping" typically indicates an indentation problem. If you change something, don't forget to save your files before debugging.
Last warning: It is possible to delete images from Docker. If you find yourself doing that, make sure that you don't delete the images associated with your application. Currently, Docker Compose attempts to recycle images from previous builds and will refuse to run your application if it can't find an earlier image. The only way I've found to solve this problem is to uninstall Docker and re-install it. Even then, I had to copy the project to a new location to get Docker to regenerate the containers.
To put it another way: If you don't know how to delete containers, ignorance is bliss. This is one problem that you can't inflict on yourself.
My sample project still isn't very realistic, however: Where's my data going to come from? In a future column, I'll show you how to create a container that holds SQL Server, how to load data into that "containerized" instance of SQL Server and how to include that container into this Solution to be used from either the MVC application or the Web Service ... or any other project that could use that data. I'll warn you, though: It does involve a (shudder) PowerShell script.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.