Chapter 4. “Hello World” for the Web

Now that we’ve successfully created the “Hello World” console application, let’s take the same idea and create a simple “Hello World” application for a browser.

To do that, we first need to install ... nothing more.

No web server, no IIS...nothing. Why is that? Because in .NET Core, a web app is a console app. That’s right—you start it from the command line and shut it down with Ctrl-C, just like any continuously running console application.

How do web pages get served? Introducing: Kestrel.

In the natural world, a kestrel is a small falcon that is built for speed. Likewise, the Kestrel web server is built for speed, with non-reviewed benchmarks coming in at 5.2 million requests per second, eight times faster than Node.js and four times faster then Go.

That’s flying!

Associated source code

The source code can be found in the /NetOnLinuxBook/HelloWeb/ directory.

The Kestrel Web Server

So how do you access this speedy server? If you don’t install anything, what do you need to do? Let’s take a look.

Start by forking and cloning (or downloading) the code at the NetOnLinuxBook repo and move into the HelloWeb directory.

Viewing the contents, we immediately notice a few things:

$ ls -altr
total 336
drwxrwxr-x. 8 vagrant vagrant   4096  Jul  9 09:49 ..
-rwxrw-r--. 1 vagrant vagrant 327085  Jul  9 09:50 
drwxrwxr-x. 3 vagrant vagrant     18  Jul  9 09:53 obj
drwxrwxr-x. 3 vagrant vagrant     18  Jul  9 09:53 bin
-rw-rw-r--. 1 vagrant vagrant    444  Jul  9 09:53 project.json
-rw-rw-r--. 1 vagrant vagrant    502  Jul  9 10:30 Program.cs
-rw-rw-r--. 1 vagrant vagrant    421  Jul  9 10:30 Startup.cs
drwxrwxr-x. 4 vagrant vagrant    103  Jul  9 10:32 .

Where is wwwroot? Where is web.config? Where is everything?

Relax—everything we need is here. Because of the new modular programming model for .NET, everything we need is in code. Let’s start by taking a look at the project.json file because we know that’s where the bits get installed:

  "buildOptions": {
    "emitEntryPoint": true,
    "debugType": "portable"
  "dependencies" : {
    "Microsoft.NETCore.App": {
        "type": "platform",
        "version": "1.0.0"
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0"

  "frameworks": {
    "netcoreapp1.0": {

The project.json File

Starting at the top and working our way down, let’s take a closer look at each line and what it represents:


(This was "compilationOptions" until shortly before the release of .NET Core, so if you encounter that in a project file, you can change it.)

Just as you might expect, these options are enforced when building the application.


If true, the build will create an executable. Otherwise, a DLL will be built.


This is where you keep a list of the dependencies (libraries/DLLs) that are needed by the project. The name of the dependency is followed by version information.


An example of a dependency. In this case, a set of .NET APIs that are part of the .NET Core application model. If you really want to see what this encompasses, check out the package on

"type": "platform"

This defines when you will need this dependency. The value "platform" means you expect this dependency (in our case, Microsoft.NETCore.App) to be available on the platform on which your application will run. In other words, it is part of the environment and not part of your application.

The value "build" means the library or libraries must be available during the build phase of your application, and they will be included with your application. They do not need to be previously installed on the machine where your app will run.

"version": "1.0.0"

The server version of the dependency you are using/installing. If you’re using an IDE to edit your program, IntelliSense will prompt you. Or, you can find the appropriate page on and see the version history.

“Microsoft.AspNetCore.Server.IISIntegration”: “1.0.0”

These are the components needed to work with the IIS module. In our example, using Kestrel in Linux, we don’t need this. However, if we remove it, we get the following error when building our application:

Startup.cs(4,28): error CS0234: 
	The type or namespace name 'HttpOverrides' does not 
	exist in the namespace 'Microsoft.AspNetCore' (are
 you missing an assembly reference?)

Line 4 of Startup.cs reads as follows:

using Microsoft.AspNetCore.HttpOverrides;

If we look at the Microsoft.AspNetCore.Server.IISIntegration page at, we can see that one of the dependencies for this library is "Microsoft.AspNetCore.HttpOverrides". Because we don’t need all of the IIS integration bits, we can remove the reference in our project.json file.

“Microsoft.AspNetCore.HttpOverrides”:" 1.0.0”

Not only does this work but this is a perfect example of the modularity of .NET and how you can select only those bits you need. This lowers memory usage and improves performance.

“Microsoft.AspNetCore.Server.Kestrel”: “1.0.0”

This gives us the Kestrel web server.

“frameworks”: “netcoreapp1.0”

Defines the framework(s) you are targeting. Perhaps more useful, you can use this section to “lie” to the compiler, allowing you to include libraries that were written for your framework but that are, in fact, compatible. In other words, if you need a library that is targeted at Portable Framework 4.5 and Windows 8, you can add an “options” section to the frameworks, such as the following:

"frameworks": {
  "netcoreapp1.0": {
     "imports": "portable-net45+win8"

A full explanation of all the project.json file values can be found at the project.json reference page.

Program.cs: Where the Magic Happens

With a better grasp of the project.json file, let’s look at another change. Opening the file Program.cs, the newness continues:

// HelloWeb
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;

namespace HelloWeb
    public class Program
        public static void Main(string[] args)
            var host = new WebHostBuilder()


Looking at the source code, you see how the web server is built in code rather than specified in one or more configuration files. Certainly, you can (and should and will) store settings in configuration files, but the task of creating a web server is now on the developer in code, not on the framework via configuration.

Notice we are using Kestrel, the web server. The ".UseContentRoot" function sets where the web server starts looking for content. Using the current directory is simple, but you can obviously change this for, say, security reasons.

The .UseStartup function is a dependency injection point, where you specify the name of the startup library. The .UseUrls() function tells the web server which address and port to watch.

The host.Run() function launches the web server. Because this is a console app, you can observe what’s happening simply by watching the stdout (i.e., the messages to the console). Simply run the dotnet run command and watch Kestrel take over, as shown in Figure 4-1.

Figure 4-1. Results of HelloWeb

To see where the web server does its work, look inside Startup.cs:

// Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;

namespace HelloWeb
    public class Startup
        public void Configure(IApplicationBuilder app)
            app.Run(context =>
                return context.Response.WriteAsync(
    "Hello World Wide Web!");

As you can see, when this Startup class is executed, it asynchronously returns a response. Very simply, it sends the “Hello World Wide Web!” response. Because this is not an MVC application, there is no routing or filtering—there’s only a simple response.

This is a minimal web application. It doesn’t support MVC or static content. In the next chapter, we’ll create and examine an ASP.NET MVC application.

Get Transitioning to .NET Core on Red Hat Enterprise Linux now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.