This blog post gives you an introduction to .NET Core and how you can deploy a .NET Core application to Cloud Foundry from scratch.
The latest version of .NET Core, 1.1.0, was released on November 16th 2016 (Announcement) and brings some improvements across .NET Core, ASP.NET Core and EF Core that will make your apps better and faster.
.NET Core is cross-platform, supporting Windows, OS X and Linux, and can be used in device, cloud, and embedded/IoT scenarios (.NET Core Guide).
As .NET Core now supports Linux, it is possible to run apps in a Cloud Foundry Ubuntu container. For that, you build your .NET Core app and deploy it with the .NET Core buildpack to Cloud Foundry. Your app has to be cloud-ready (Considerations for Designing and Running an Application in the Cloud)
Currently, the LTS version of .NET Core is 1.0.1 and the latest version is 1.1.0. We will use both in our examples. As a source code editor we use Visual Studio Code as it can be used on different plattforms like Windows, Linux and OS X.
To get started, you have to:
- Install .NET Core SDK
- Install Visual Studio Code
- Setup an account on a Cloud Foundry platform
- Install the Cloud Foundry CLI
- Write some code
- Deploy the app to Cloud Foundry
An installation guide can be found here: https://www.microsoft.com/net/core
Please install the .NET Core 1.1 SDK so you are able to build apps for 1.1.0 and 1.0.1 . The SDK can be found on https://www.microsoft.com/net/download/core#/current. Download and install the SDK that fits your OS.
Visual Studio Code is a cross-platform source code editor. You can use it on Windows, Linux and OS X. It supports debugging, git control, syntax highlithing, intelligent code completion, snippets, and code refactoring. It's free and open source. You can use it also for additional languages by installing extensions. If you are used to working with other source code editors or IDEs (like Visual Studio or IntelliJ) with support for .NET Core you don't have to switch.
Visual Studio Code is available on https://code.visualstudio.com/Download
C# language support is an optional install from the Marketplace. You can install it from within Visual Studio Code (Installing C# support).
To get familiar with it check out the following links:
Maybe you are working behind a corporate proxy. In this case you have to set the proxy settings in Visual Studio Code.
You can do that by modifying the settings.json
file under the menu item "File/Preferences/User Settings":
{
"http.proxyStrictSSL": false,
"http.proxy": "http://proxy.example.com:8080",
"https.proxy":"http://proxy.example.com:8080"
}
Choose your prefered Cloud Foundry provider and sign up for an account.
You can find a list of certified Cloud Foundry Providers on https://www.cloudfoundry.org/use/cloud-foundry-certified. If you choose one of these providers you are certain that your app will run as expected and you could also switch the provider later and your app will still work.
For example, you can sign up for the Swisscom Application Cloud. To start using it, here's the link to the documentation.
Follow the instructions to install the CLI.
- First we will set up a new .NET Core "hello world" console application.
- Then we will turn it into a web application and also into a MVC API application.
- Afterwards, we will prepare it to use the Cloud Foundry buildpack.
To create a new project, open a new console/terminal and follow these steps:
- Create a new folder/directory
- Change to the folder
- Run
dotnet new
to create a new project - Run
dotnet restore
to fetch all required NuGet packages linked in the project - Run
dotnet run
to execute the app
$ mkdir hello
$ cd hello
$ dotnet new
$ dotnet restore
$ dotnet run
and you will get "Hello World!" in the output:
Now we open the "hello" project with Visual Studio Code (or your editor of choice) and we modify it to become a web application.
- In Visual Studio Code, under the menu item "File/Open Folder..." select the "hello" folder and open it.
- Confirm all messages from Visual Studio Code.
- Open the
Project.cs
file.
The source code:
using System;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
The file contains a public Main()
function, just like you expect for a classic console application. All .NET Core applications, including ASP.NET applications, are now console applications.
To turn the app into an ASP.Net web application, we have to add a self-hosted web server. Currently, for cross-platform applications, we have to use the Kestrel server.
Starting with version 1.1 Preview 1, on Windows you can also use WebListener as a web server. WebListener gives you the option to take advantage of Windows specific features.
For now, we want to build an application with version 1.0.1.
Therefore, we change the references in project.json
to use version 1.0.1
.
We will switch to 1.1.0
later in this blog post.
For that we have to:
-
Add some namespaces:
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http;
-
Modify the
Main()
method to use the Kestrel web server:var host = new WebHostBuilder() .UseKestrel() .UseStartup<Startup>() .Build(); host.Run();
-
Add an additional class
Startup
with aConfigure()
method in it:public class Startup { public void Configure(IApplicationBuilder app) { app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } }
-
Add a reference in the
project.json
to pull in the Kestrel web server and HTTP extensions:"dependencies": { "Microsoft.AspNetCore.Server.Kestrel": "1.0.1" },
-
Change all references to
1.0.1
and the framework version tonetcoreapp1.0
.
The whole modified Program.cs file:
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
}
}
The modified Project.json
:
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1"
},
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.1"
}
},
"imports": "dnxcore50"
}
}
}
After we have made the change to the project.json
, Visual Studio Code asks for restoring. Confirm the message.
You can do that also manually in your console/terminal or in the integrated terminal of Visual Studio Code (menu item "View/Integrated Terminal")
This updates the NuGet packages and pulls the additional package.
Then run either via console/terminal or the integrated terminal:
$ dotnet run
You can also add an additional task in Visual Studio Code which you can call to run the app (see https://code.visualstudio.com/docs/editor/tasks)
If there are no exceptions, you will be informed that the web server is listening on port 5000:
Open http://localhost:5000 and ... there it is!
ASP.NET Core applications are self-contained console applications with an integrated web server.
The configuration is delegated to the Startup
class. For simplicity, the class in this post is in the same file.
The Startup
class is where you can hook up custom middleware that is added to the ASP.NET pipeline.
Middleware is essentially what we used to call "HttpHandlers" and "HttpModules" in classic ASP.NET.
The code in the Startup class above uses the App.Run()
middleware handler,
which is roughly the equivalent of an HttpHandler in classic ASP.NET.
When App.Run()
gets fired it is expected to handle inbound requests completely and return a result.
App.Use()
is like an HttpModule in classic ASP.NET.
Handlers can be chained together and are fired in the order they are assigned.
Unlike HttpModules, middleware can handle both before and after request processing operations.
So you could add the following method just before the App.Run()
method in our example:
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Pre Processing...");
await next();
await context.Response.WriteAsync("...Post Processing");
});
And as a response, you would see:
Pre Processing...Hello World!...Post Processing
App.Use()
and App.Run()
are very low level constructs.
When you build a web application you tend to use much higher abstractions such as ASP.NET MVC.
ASP.NET Core has built-in support for web APIs with an MVC architecture. Unifying the two frameworks makes it simpler to build apps that include both UI (HTML) and APIs, because now they share the same code base and pipeline.
To turn the app into an MVC API application with a single simple API endpoint HelloWorld
, we have to:
-
Replace the
Microsoft.AspNetCore.Http
namespace with two other namespaces:using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Mvc;
-
Modify the
Configure()
method:public void Configure(IApplicationBuilder app) { app.UseMvc(); }
-
Add an additional method
ConfigureServices()
to theStartup
class:public void ConfigureServices(IServiceCollection services) { services.AddMvc(); }
-
Add a
HelloWorld
controller class:[Route("[controller]")] public class HelloWorldController : Controller { [HttpGet] public string Index() { return "Hello World!"; } }
-
Add an additional reference to the
project.json
file:"dependencies": { "Microsoft.AspNetCore.Mvc": "1.0.1" },
The whole modified Program.cs
file:
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc;
namespace MyApplication
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
[Route("[controller]")]
public class HelloWorldController : Controller
{
[HttpGet]
public string Index()
{
return "Hello World!";
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
}
}
}
This code sets up the required dependency injection in ConfigureServices() and then enables MVC for the application in Configure().
The modified Project.json
:
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.AspNetCore.Mvc": "1.0.1",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1"
},
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.1"
}
},
"imports": "dnxcore50"
}
}
}
Restore and run the app. When calling http://localhost:5000/helloWorld
you get "Hello World!" as a response.
An MVC API application is deployed to Cloud Foundry by using the .NET Core buildpack. You can imagine a buildpack as instructions to set up the runtime environment of your application.
Roughly, the following steps are involved during the staging process:
- Creating the app (if not already done)
- Creating a route and binding it to the app (if not already done)
- A temporary container (Ubuntu) is created
- The application bits are uploaded to the container
- The buildpack itself is downloaded to the container
- The buildpack is executed:
- It downloads additional dependencies (runtimes, frameworks, web servers)
- It installs and configures all the necessary components
- The whole setup is stored in a so-called droplet and the droplet is saved
- The temorary container is destroyed
- Starting the application in one ore more running containers
- The droplet is restored to the containers
- The app is started
Cloud Foundry will automatically assign each application instance an IP address and port. Each instance of your app may receive a different IP address and port. The .NET Core buildpack configures the app web server automatically so you don't have to handle this yourself. But you have to prepare your app in a way that allows the buildpack to deliver this information via the command line to your app.
Cloud Foundry will start your app with the following command: dotnet run --server.urls http://0.0.0.0:${PORT}
For that we have to:
-
Add an additional namespace:
using Microsoft.Extensions.Configuration;
-
Modify the Main() method:
var config = new ConfigurationBuilder() .AddCommandLine(args) .Build(); var host = new WebHostBuilder() .UseKestrel() .UseConfiguration(config) .UseStartup<Startup>() .Build();
-
Add an additional reference to
project.json
(as there is no1.0.1
version for CommandLine so we use1.0.0
instead):"Microsoft.Extensions.Configuration.CommandLine": "1.0.0",
Restore the project and run the application on a different port than 5000 (default port):
$ dotnet restore
$ dotnet run --server.urls http://localhost:5001
Your application endpoint should now be available on port 5001.
Cloud Foundry automatically uses the .NET Core buildpack when:
- The pushed app contains one or more
project.json
files. - The app is pushed from the output directory of the dotnet publish command.
Currently, the buildpack has an issue for option 1: The app is compiled just before starting (running container) instead of during the staging phase.
Therefore, most likely, the build process needs more memory than your app needs at runtime and you need to configure your Cloud Foundry app with more memory.
Secondly, the NuGet cache is also stored in your container and uses several hundred megabytes disk space.
This issue will be fixed by the buildpack developers as soon as Microsoft has switched from project.json
to MSBuild projects.
For now, it's better to go with option 2 as it has a lower footprint.
Steps to deploy the application to Cloud Foundry:
- Publish the application with the
dotnet publish
command and use the optionRelease
- Go to the
bin/Release/netcoreapp1.0/publish
folder - Log in to Cloud Foundry with the CF CLI
- Deploy the app to Cloud Foundry
$ dotnet publish -c Release
$ cd bin/Release/netcoreapp1.0/publish
Get your credentials (API-endpoint, user, password) from your Cloud Foundry provider and log in. For example, if you are using the Swisscom Application Cloud:
$ cf login -a https://api.lyra-836.appcloud.swisscom.com -u myUserName
(you will be asked for your password during the login process)
If you are behind a corporate proxy you have to configure the proxy first:
Windows:
$ set http_proxy=proxy.example.com:8080
$ set https_proxy=proxy.example.com:8080
Linux:
$ export http_proxy=proxy.example.com:8080
$ export https_proxy=proxy.example.com:8080
If not already done, create an org and a space and then target the space:
$ cf create-org myCFOrg
$ cf target -o myCFOrg
$ cf create-space dev
$ cf target -s dev
Finally, deploy the app:
$ cf push myCFApp
Cloud Foundry has automatically used the .NET Core Buildpack as we have pushed the app from the publish
folder.
If everything went well, you can call your app using its route's URL. You can retrieve that URL using the cf app
command:
$ cf app myCFApp
In my example the URL is mycfapp.scapp.io
. As you can see, the memory and disk usage is relatively small. With option 1 it would be much higher.
Then open the URL http://mycfapp.scapp.io/HelloWorld. Et voilà!
To do that, we modify the references and the framework version in project.json
:
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.Extensions.Configuration.CommandLine": "1.1.0"
},
"frameworks": {
"netcoreapp1.1": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
}
},
"imports": "dnxcore50"
}
}
}
We also have to add a global.json
file and add some content to it. Otherwise, the buildpack will use an older version of .NET CLI to run the app:
{
"sdk": {
"version": "1.0.0-preview2-1-003177"
}
}
This is the SDK version and not the framework version.
Publish again and go to the version 1.1 publish folder:
$ dotnet publish -c Release
$ cd bin/Release/netcoreapp1.1/publish
Deploy the app:
$ cf push myCFApp
... and it has failed!
Let's look at the logs:
$ cf logs myCFApp --recent
It indicates that framework version 1.1.0 was not found.
By default, the system buildpack is used. The system buildpack is updated with Cloud Foundry. In the current installed Cloud Foundry version the latest .NET framework 1.1.0 is not included. This will be fixed by one of the next releases. The online version of the buildpack is able to use the latest framework version by downloading it.
This behaviour can occur anytime if you are using the latest version of .NET Core. Therefore, we deploy our app with the online buildpack:
$ cf push myCFApp -b https://github.com/cloudfoundry/dotnet-core-buildpack
This time, everything went well!
- Swisscom Application Cloud: .NET Core Tutorial
- Swisscom Application Cloud: .NET Core Buildpack
- Swisscom Application Cloud Webinar: Pushing .NET Apps to Cloud Foundry
- Swisscom Application Cloud: Developer Documentation
- Rick Strahl's great Blog Post: Exploring .NET Core and ASP.NET Core
- Matthew Jones Blog Post: The Startup.cs File in ASP.NET Core
- Microsoft: .NET Core Guide
- Microsoft: .NET Core command-line interface tools
- Microsoft: .NET Core Tutorials
- Microsoft: Using MSBuild to build .NET Core projects
- Microsoft: .NET Core Application Deployment