As a professional .NET developer, writing apps for Linux probably hasn't been on your radar. Why would you even want to? Linux is for hipsters with MacBooks who write in Java and for some inexplicable reason, prefer to work in arcane and garishly colored command shells, right?
Additional Azure related articles: Microsoft Azure CODE articles
Find additional: .NET Core articles
All kidding aside, you're probably asking where this all fits in to the corporate world in which so many of us make our livings. Not including the large number of potential customers who prefer a non-Windows infrastructure, we'll be seeing more and more of Linux in the Microsoft-centric world and the reasons are mainly economic. Customers with large infrastructures need fault tolerant, scalable, secure, and inexpensive environments. In short, they need proactive, robust DevOps without breaking the bank.
Why Linux? Hasn't it been proven that the Total Cost of Ownership (TCO) of Windows is less than Linux? About four years ago, an open source project called Docker launched and changed the IT world. The concept wasn't new, but the implementation was good and began to take hold. Docker allowed us to create runtime sandboxes called containers to deploy our apps in. Once configured for the container, the apps ran happily without knowing or caring about anything outside of the container. This allowed the containers to be run on any computer. They could be moved around, scaled out and made fault-tolerant by creating multiple instances behind load balancers and treating them like a commodity. Apps could be bundled together in the same container or split up into separate containers. Imagine a system with 50 micro-services, each one being developed and deployed independently. You could run all 50 on a single computer or run one instance each on 50 computers or 50 instances on one computer and another 50 on a second computer, or x instances on y computers. Now imagine being able to get any of these scenarios up and running in a couple of hours and being able to change from one scenario to the next in even less time. That's pretty powerful stuff and when you consider that each of these containers only requires a few MB and can run in tiny amounts of memory compared to a big, fat .NET application, you can see why everyone's been talking about Docker.
Until a few months ago, Docker and Linux didn't generate a lot of interest for .NET developers. If you couldn't write apps for Linux, why would you care? All of that changed when three things happened. First, Docker worked with Microsoft and started allowing us to host containers on Windows 10 and Windows Server 2016. In fact, they introduced two types of container hosting, one similar to Linux and one that Linux users didn't have. It's based on Hyper-V and, although much heavier in terms of resource requirements, provides much better isolation between containers and thus, much better security. Second, Docker started supporting Windows Server Core and Nano containers (on Windows Docker Host computers), so our apps could run in Windows containers on Windows hosts. Yes, that's right! We can run IIS and the full ASP.NET stack as well as other Windows software in containers now. Although Windows containers tend to be a lot bigger and heavier than their Linux counterparts, we can now begin to do a lot of the same DevOps tricks that our Linux counterparts have been enjoying. We can for example, package our websites in containers running Windows and IIS and ASP.NET in a container and do the same for our Web API micro-services and enjoy the benefits of Docker.
Then, there's that third thing that happened. Microsoft created something called .NET Core that runs not only on Windows, but on Macs and Linux and a host of other platforms including Internet of Things (IOT) devices, tablets, and phones. Given the choice between writing C# programs that only run on Windows computers or writing the same program that can run on Windows, Linux, Mac, or a Raspberry Pi, why would I choose to only run on Windows? ASP.NET Core is new and just hit v1.1. It's not as complete or robust as the full .NET stack. You can't program desktop UIs in it (at least not yet). Although you can build some basic websites with .NET Core, it's not nearly as powerful as its big brother, but the services portion is looking good and that's one place .NET Core shines. Services in .NET Core are almost identical to services in ASP.NET Web API. In fact, I've copied and pasted quite a lot of my existing Web API code into .NET Core services and had it work perfectly on the first try.
If I can run my services on the smaller, faster, cheaper Linux containers and not only make my way into that large, untapped market but also offer those benefits to my existing clients at no extra cost to me, why wouldn't I embrace it? I can always run the very same code in Windows containers or on Windows proper if my client prefers it.
Create a .NET Core App
How can you get all this goodness too? First, you have to have either Windows 10 Professional or Enterprise, or Windows Server 2016 Essentials, Standard, or Data Center. And you have to be running a 64-bit OS. Go to https://www.docker.com/products/docker#/windows and download the appropriate Docker for Windows for your OS. The installation is simple and you can easily accept all the defaults. Next, you're going to want Visual Studio 2017. As I write this, it's in Release Candidate (RC) status. When you install, make sure to check the NET Core and Docker component. Of course, you don't need Visual Studio 2017 to build .NET Core apps; you can do that with Notepad and the .NET Core SDK, but we're not savages! We're used to good tools. This is all you need to build a service and run it in a Linux container.
We're not savages! We're used to good tools.
Under Visual Studio's File menu, choose New Project. Under the Visual C# Templates, choose ASP.NET Core Web Application (.NET Core). (You can see this in Figure 1.) Notice that Visual Studio doesn't allow you to set the framework version here (yet). It's very important in this version that you create the solution on your OS drive (normally C:). Otherwise, you may run into a bug after installing Entity Framework Core NuGet packages where the project file no longer loads. If you install on a different drive and run into this bug, try moving the project onto your OS drive.
Next, choose the Web API template, set it to No Authentication, and check the Enable Container (Docker) Support checkbox, as shown in Figure 2.
Open the Output window and pin it open. Also, make sure you have an Internet connection. Notice that the Run button says Docker
. Press it to begin the build and run process. The build is going to be a bit unusual and you'll notice that it can take a long time the very first time you build. By choosing to Enable Container (Docker)
support, you asked Visual Studio to create a container image for you and to run and debug the code inside the container. You'll see the Docker command lines being run in the Output window. If the build fails, check your Docker Settings and make sure that the drive you're using for your source code is shared.
After compiling the code and setting up an internal network for the computer and the containers to communicate, you'll see a line that looks something like this: Step 1: FROM microsoft/aspnetcore:1.0.1. Docker downloads a Linux image with everything required to run ASP.NET Core already installed. Next, it adds an image in which to install your app. Containers are “composed” by layering one image on top of another to get all of the functionality you need. By maintaining the original ASP.NET Core image without modification, Docker won't have to download that container again the next time you build any project based on that image.
The very first build may take several minutes. Be patient. Eventually, you'll get a browser showing some JSON, the same output as the project template for ASP.NET Web API project. Leave the app running, open the Values controller in the Solution Explorer and put a breakpoint inside the initial Get()
method, and then refresh your browser. Congratulations! You're debugging an application that's running on Linux in a Docker container.
Next, you'll upgrade from .NET Core 1.0.1 to the latest version (currently 1.1.0). The process is still a bit messy, but will probably improve before VS 2017 is in RTM. A lot of the work you'll want to do requires the latest versions, so it's worth the time to learn how to upgrade. Right-click on the project file and choose Manage NuGet Packages. Select the Updates
tab and update all of the packages. You may have to do more than one round of updates to get them all updated. Right-click on the project again, choose Properties
to open the project properties page and change the Core framework
version from 1.0 to 1.1. Finally, open Dockerfile
(no extension) in the Solution Items
folder for editing. The top line says FROM microsoft/aspnetcore:1.0.1. This is the base image that Docker downloads to create your container (before your app is layered on). You can find information about the latest containers posted by Microsoft at https://hub.docker.com/r/microsoft/aspnetcore. Edit the line to read: FROM microsoft/aspnetcore:1.1.0. That's the latest version as I write this. Rebuild as a .NET Core 1.1 app running on a Linux image with .NET Core 1.1 pre-installed.
Create a Database
This article isn't about how to create databases or write Web API services, but let's do something a little more interesting than return some static text. Create a new SQL Server database (use SQL Server 2016 if you plan to move this to Azure), name it as you like, and then run the script included with the download for this article or manually create a new table with the columns and then add a few rows of test data, as in Table 1.
Because you'll be hitting the database from Linux, you won't be able to use a trusted connection (Windows credentials) to connect to the database, so you'll create a login and user named devuser with password P@ssw0rd
(or choose your own user name and password, but remember that if you push the database up to Azure later in this article, the password will have to meet Azure's strict password requirements) and give that user permission to read and write all tables. If you haven't downloaded the script, you'll need to add these manually using SSMS. Test that you can log in as devuser and read and write the Person table. This is important, because if you can't connect with SSMS, your service won't be able to connect either.
Install Entity Framework Core
Next, you'll use Entity Framework Core (EF Core) to talk to the database. Back in Visual Studio, right-click on the project and select Manage NuGet Packages. Choose the Browse tab and search for and install the following:
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.SqlServer.Design
- Microsoft.EntityFrameworkCore.Tools
For the last one, you may have to check the Include Prereleases
checkbox on the search screen. As I'm writing this article, there's still not a released version of this package. This package adds some development-time capabilities to the NuGet Package Manager Console
, including the ability to generate Code First EF classes from your existing database. Yes, Code First migrations also work if you prefer that approach, but both features are still in preview. You can find more information about the tools at http://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell.
Under the Tools menu in Visual Studio, select NuGet Package Manager > Package Manager Console. Type in the following command at the prompt (substituting the name of your server, database and login credentials as necessary).
scaffold-dbcontext "Server=mycomputer;
Database=AzureDockerServices; User Id=devuser;
Password=P@ssw0rd"
Microsoft.EntityFrameworkCore.SqlServer
?OutputDir EFModels
This command adds a folder named EFModels
to your project and then generates a context class and a class for each table in that folder. EF Core is very much a V1 product. It's not nearly as complete and robust as its predecessors, however all of the basics are there to make most common tasks possible and future versions promise to be better than the EF we use today. If you need more complex interactions, .NET Core has many database connectors, including one for SQL Server that you can use to perform any activity still out of the reach of EF Core.
Create a Web API Service
Right-click on the Controllers
folder, select Add > New Item. Under ASP.NET Core, Web, ASP.NET choose Web API Controller Class and name it PersonController.cs
. You won't be using the sample methods, so delete them to prevent a conflict. Paste the following method into the class.
[Route("{searchText}")]
[HttpGet]
public List<Person> SearchPeople(string searchText)
{
using (var context = new AzureDockerServicesContext())
{
var people = context.Person.Where(p => p.FirstName.StartsWith(searchText)
|| p.LastName.StartsWith(searchText)
|| p.Email.StartsWith(searchText)
|| p.Phone.StartsWith(searchText)).ToList();
return people;
}
}
Run the app again. When the browser appears, replace /api/values in the URL with /api/person/m. This calls your method in the person controller and returns all records where the first name, last name, email, or phone number starts with the letter m. If you run into a SQL error, edit AzuredickerServicesContext.cs
and try using your IP address instead of your computer name. The Linux computer running in Docker may not be able to do a DNS lookup on your computer name if you're not connected to a network. I've run into this a few times while travelling with my laptop. I found that 127.0.0.1 doesn't work either. Run ipconfig at a command prompt and use the IP4 address listed there. You might have noticed that the code you wrote would work equally well in a standard ASP.NET MVC project.
Push the Database to Azure
Now that you have a working service on your dev computers, let's deploy the whole thing to Azure. Start by deploying the SQL Database. I'm assuming that you have an Azure account. If not, you can create a free trial account and use that for testing. Use the Azure portal to create a SQL Server if you don't have a server set up already. Make sure that you can connect to the server in Azure with SSMS. Add the devuser
login to the server so that when you push the database up to Azure, you'll be able to push the devuser
user as part of the database.
Open SSMS 2016 login to your local database making sure you're logged in as an admin and not as devuser. Right-click on the AzureDockerServices
database. Choose Tasks > Deploy Database to Microsoft Azure SQL Database. Log into your server in Azure. You can accept the defaults to keep the database name and the Basic edition, which has a 2GB size limit. Cost for this database is about $5/month if you keep it for the whole month. Finish the wizard to push your database up to Azure. Using SSMS, log in to the Azure server as devuser, and make sure that you can read and write the person table.
Open AzuredockerCoreServicesContext.cs
in the EFModels
folder and change the name of the server to point to the database in Azure. Test the service to ensure that it's still working properly.
Push the Container to Azure
The final step is to publish the Docker container with your services app to Azure. For now, you're just going to publish a single copy of the Linux container. If it works there, the same container can be deployed with Docker Swarm or some other container orchestration product for more advanced deployment and scalability scenarios.
Right-click on the project and choose Publish
. On the Publish tab, click Create
to create a new deployment profile. Choose Azure App Service Linux (Preview). Change the Web App Name if you like, choose the Azure subscription you want to use, and create a new Resource Group. I named mine Docker-WestUS-ResourceGroup
so I can easily tell which data center region I'm using. Create a new App Service Plan. I named mine Docker-WestUS-Plan
. Next, create a new Container Registry. I named mine DockerWestUSRegistry
. Container Registry names cannot contain dashes and the error message is misleading. Note that currently only West Europe, Southeast Asia, and WestUS regions offer Linux App Services. Your Resource Group, App Service Plan, and Container Registry must all be in the same Azure region. Don't use an existing Resource Group unless you know for sure it's in the same region as the plan and registry.
Your Resource Group, App Service Plan, and Container Registry must all be in the same Azure region.
Once you publish, the app builds and deploys. The deployment in the Output window happens pretty quickly, and then a command window opens and you'll see your image being pushed up to Azure. In Docker lingo, an image is a file and a container is a running instance of that file. Images are stored in repositories. When you want create a new container, you make an image and deploy a copy of the image to a repository. When you want to spin up a new container, you get a copy of the image from the repository and start it up on some hardware.
Unlike deploying an ASP.NET MVC and/or Web API project, publishing doesn't automatically start up a browser for you to go try it out. A link to the URL shows up in the Web Publish Activity window, however. If you can't find it, log into your Azure portal, open up Web Apps, find your new app, and use the link on the overview page. Initially, you get a 404 error. Don't forget that you have to add /api/person/m to the end of the URL to hit the service.
Unlike deploying an ASP.NET MVC and/or Web API project, publishing doesn't automatically start up a browser for you to go try it out.
Summary
Well done! You created a Linux app that hits SQL Server and ran it not only in your desktop environment but also in the cloud. You're now a certified services developer for Docker.
Services are the first things you expect to see built with .NET Core. Even though it's a brand new product and will grow more powerful with each new version, there's already enough in this portion of .NET Core to make it worth giving a serious look. Websites and other types of projects are sure to follow, but they're not as far along as services. Because .NET Core can run on a variety of platforms, it makes sense to take a serious look at Linux. And because Docker now runs on Windows, it makes sense to take a serious look at Docker. These are hot job skills and they're getting a lot of press for good reason. As I review this before sending it off to my editor, a pop-up just notified me that a new version of Docker has just been released, version 1.13, the one with support for Windows containers is now in RTM. That's an article for another day. It's a fast-paced world and moving faster all the time, but it's not a big stretch for a C# developer to hop on and see what all the fuss is about.
Table 1: The test data
Id | UNIUQEIDENTIFIER | NOT NULL | DEFAULT: newid() |
FirstName | NVARCHAR(50) | NOT NULL | DEFAULT: '' |
LastName | NVARCHAR(50) | NOT NULL | DEFAULT: '' |
oEmail | NVARCHAR(100) | NOT NULL | DEFAULT: '' |
Phone | NVARCHAR(50) | NOT NULL | DEFAULT: '' |