home

The Smallest Possible ASP.NET Web Application

One of the things that I like about Go is the minimalistic http support built into it’s standard library. I wanted to see if I could replicate this style of development in C#. Now that ASP.NET comes with the .NET SDK, I believe this is possible. The first step in this journey was to ask the question: What is the smallest amount of code in C# to make a Hello World web application.

Update 2021-08-02: It looks like Microsoft has embraced this style of development with the new minimal api. I haven’t yet updated this article though.

Updated 2021-02-10: The examples below have been updated to use top level statements in .NET 5.

First, I created a new console project with dotnet new console

In order to use the AspNetCore libraries, I had to edit the project file (.csproj) and change the first line to use the web sdk like below.

<Project Sdk="Microsoft.NET.Sdk.Web">

After some experimentation, I ended up with the program below. Running dotnet run will listen on port 5000 and print out hello world over http (but nothing on the console).

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
WebHost.Start(c => c.Response.WriteAsync("Hello World"))
    .WaitForShutdown();

Note: The Start method returns a disposable. You should probably wrap it in a using statement, but I didn’t here because the program was exiting anyway.

Version 2: Custom url/port number

Another overload of the Start method allows you to specify a custom url or port number to listen on.

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
WebHost.Start("http://localhost:5002",
              c => c.Response.WriteAsync("Hello World"))
    .WaitForShutdown();

Version 3: Endpoint routing

Yet another overload of the Start method allows you to route between endpoints and even extract route values.

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
WebHost.Start("http://localhost:5002", r => r
            .MapGet("", async c => await c.Response.WriteAsync("Hello World"))
            .MapGet("/hello/{name}", async (req, res, d) => 
                await res.WriteAsync($"Hi There {d.Values["name"]}")))
    .WaitForShutdown();

Version 4: Fewer lambdas

If lambdas inside of lambdas aren’t to your liking, here is a slightly more verbose version of the same thing.

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;

using var host = WebHost.Start("http://localhost:5002", RegisterRoutes);
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();

static void RegisterRoutes(IRouteBuilder router)
{
    router.MapGet("", Home);
    router.MapGet("/hello/{name}", Hello);
}
static async Task Home(HttpContext ctx)
{
    await ctx.Response.WriteAsync("Hello World");
}
static async Task Hello(HttpRequest req, HttpResponse res, RouteData data)
{
    var name = data.Values["name"];
    await res.WriteAsync($"Hi There {name}");
}

Version 5: Command line configuration

If you wanted to take this one step further, you could use the following code, which would allow you to specify the url/port number on the the command line using:

dotnet run server.urls=http://localhost:5002
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Builder;

using var host = WebHost.CreateDefaultBuilder(args)
    .Configure(app => app.UseRouter(r => r
        .MapGet("", Home)
        .MapGet("/hello/{name}", Hello)))
    .Build();
host.Start();
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();

static async Task Home(HttpContext ctx)
    => await ctx.Response.WriteAsync("Hello World");
static async Task Hello(HttpRequest req, HttpResponse res, RouteData data)
    => await res.WriteAsync($"Hi There {data.Values["name"]}");

Version 6…

If you want to expand further, you should probably just use what you get with dotnet new web. In fact, you should probably use that for most things. This was just a fun project to see what you could do with as little as possible.

References

Bonus: Dotnet Script

I wanted to be able to do something like this without needing a project file at all.
I was able to get the following code to work using the dotnet script project (https://github.com/filipw/dotnet-script).

First you need to install dotnet-script: dotnet tool install -g dotnet-script

Then save the following code in a csx file (for example main.csx)

#r "Microsoft.AspNetCore"
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
WebHost.Start(async c => await c.Response.WriteAsync("Hello World"))
    .WaitForShutdown();

You’ll need to tell dotnet to run it using the Web SDK. Create a *.runtimeconfig.json file. I named this one script.runtimeconfig.json

{
  "runtimeOptions": {
    "framework": {
      "name": "Microsoft.AspNetCore.App",
      "version": "3.1.0"
    }
  }
}

Then you can run the csx script by running:

dotnet script --runtimeconfig script.runtimeconfig.json main.csx

The extra config file doesn’t really make this any smaller than the other examples. It would be nice if they added an easier way to configure which framework to use. For now, this will have to do.

2020-02-09