I was never a ASP.NET developer, but since self-hosting on Linux only meant trouble, I gave ASP.NET vNext a try. At first, the whole DNX and .NET Core thing hit me like a hammer. A big one. Wielding a jackhammer. After working with it for a couple of days now though, I’m actually more than excited — it’s almost heresy. With all the logging, configuration services (Microsoft.Framework.Configuration
!), developer secrets, host-integrated dependency injection included by design — not to forget the Linux support — there’s absolutely no way I’ll ever go back to .NET once DNX is stable.
That said, here are some things I stumbled over:
- If you just installed Visual Studio 2015 Community and ASP.NET is still beta, make sure to install the newest beta integration. Last time I checked I was two versions behind and things change rapidly at the moment.
- When diving into this DNX (“Class Library (Package)”, “Console Application (Package)”, ASP.NET 5 web project) thing, if you don’t explicitly need .NET Core support, kick the
dnxcore50
framework out of theprojects.json
. That reduces pain time to a minimum, when otherwise you’d be searching for the cause of one gazillion build errors that should not be there. - When using the official (and sweet)
microsoft/aspnet
Dockerfile
, know that the examples are tailored for single-project solutions. Below is how I did it in the end. - At least as per beta 7, the
dnu restore
in thedocker build
process takes ages. This has likely something to do with mono and — as per this bug report — there’s a quick fix for it: AddENV MONO_THREADS_PER_CPU 2000
to yourDockerfile
and see the world with new eyes. - I also had to write a middleware for MVC 6 to support
X-Forwarded-Proto
and the official RFC 7239Forwarded
headers, more below.
First of all, I had my development site running locally in Visual Studio, available to the subnet through a local nginx reverse proxy which I would use to dispatch to any of my development websites. In this case, the .../aspnet
route would point to my self-hosted application. That nginx, in turn, was behind another nginx, listening to the https://myhost/testing
route on the actual webserver. Sadly, this base path of /testing/aspnet
was totally tripping up ASP.NET which assumed a base path of /
, also the protocol was not recognized. So I added this configuration to my local nginx:
upstream local_aspnet { server localhost:9000; } server { listen 80; server_name myhost localhost; location /aspnet { rewrite /testing/aspnet(.*) $1 break; # Handled within ASP.NET proxy_set_header X-Base-Path /testing/aspnet; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto "https"; proxy_pass http://local_aspnet/; proxy_redirect off; proxy_set_header HOST $host; proxy_buffering off; break; } }
And implemented the following in C#:
public static void UseReverseProxyProtocolRecognition( [NotNull] this IApplicationBuilder app) { app.Use(next => context => { var request = context.Request; var headers = request.Headers; if (!string.IsNullOrWhiteSpace(headers["X-Base-Path"])) { request.PathBase = headers["X-Base-Path"]; } if (string.Equals(context.Request.Headers["X-Forwarded-Proto"], Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { request.Scheme = Uri.UriSchemeHttps; } else if (!string.IsNullOrWhiteSpace(headers["Forwarded"])) { // http://tools.ietf.org/html/rfc7239 var match = Regex.Match(headers["Forwarded"], @"proto=(?<proto>https?)"); if (match.Success) { request.Scheme = match.Groups["proto"].Value; } } return next(context); }); }
which allowed me to simply do:
public void Configure(IApplicationBuilder app, IApplicationEnvironment env, ILoggerFactory loggerfactory) { // ... app.UseReverseProxyProtocolRecognition(); // ... }
As for the Dockerfile
, since I was using multiple projects in one solution, I had to cheat a little. This file works by copying the project files into the image, then running dnu restore
individually for each DNX project, then adding the whole sources. This — when putting the least modified project first — allows for caching as much as possible when rebuilding the image, while still allowing for cross-project dependencies. Also note the use of MONO_THREADS_PER_CPU
which really is a life-saver here.
FROM microsoft/aspnet ENV MONO_THREADS_PER_CPU 2000 ENV STARTUP_PROJECT src/Startup.Project COPY NuGet.Config /app/ COPY global.json /app/ COPY src/Startup.Project/project.json /app/src/Startup.Project/ COPY src/Second.Project/project.json /app/src/Second.Project.Models/ COPY src/Third.Project/project.json /app/src/Third.Project/ WORKDIR /app/src/Startup.Project RUN ["dnu", "restore"] WORKDIR /app/src/Second.Project RUN ["dnu", "restore"] WORKDIR app/src/Third.Project RUN ["dnu", "restore"] COPY . /app WORKDIR /app/$STARTUP_PROJECT EXPOSE 5004 ENTRYPOINT ["dnx", "kestrel"]
If you’re at it, check out the Docker for Windows toolkit which amazingly sucks less than it did some months ago.
Also have fun and coffee. You know I do.