ASP.Net Core, on Ubuntu — Performance, C#, MVC, NGINX, and Fun

If you are a .net developer, and unless you have been living under a rock for the past year, you’ve heard of Microsoft’s efforts to go open source, and make .net a cross-platform development framework. They have made, and are continuing to make an effort to appeal to developers across every viable platform it seems. They are proving that .net is a great framework, not only for windows, but for the other major operating systems in which most developers build solutions.

I am here to tell you that it works.

Now, I won’t say it is easy, and I won’t say that it is all unicorns and rainbows, and your compiles will not always result in “Build Succeeded”. But, once you learn the differences between asp.net core 1.0 and asp.net with MVC and WebAPI using .net 4.0 or 4.5.x, you can move along just as quickly as you would when developing your traditional asp.net MVC system in Visual Studio. Or, you can use Visual Studio Code, an atom based editor which you can download here — https://code.visualstudio.com

For those who have developed asp.net MVC and WebAPI systems under .net 4.0 or 4.5.x, you have access to a HUGE library of .net objects. With asp.net core, you do not have that. Currently in .net core, everything is lean and mean, and you do not have access to the full functionality that many of us are familiar with in 4.0 or 4.5.x. A prime example — SMTP libraries. We have the System.Net.Mail.MailMessage and other associated classes in the full framework. That’s not the case in .net core. To send an SMTP email, you need to roll your own library, find one on Github, or use a service. I opted for the service. A good option that I found is MailGun. Give it a try.

Microsoft provides a great set of instructions for setting up .net core for each platform at https://docs.asp.net , and I have had success on CentOS 7, Ubuntu 14.04, and Mac OSX El Capitan with the builds of my primary asp.net core project. Performance has been very nice, and quite impressive.

Where do I start?

  • https://docs.asp.net
  • Download the appropriate version for your framework
  • Learn about dnvm, the .net version manager, it helps you obtain the latest updates from Microsoft for the different .net coreclr libraries (common language runtime) My installs current stand at:
Active Version Runtime Architecture OperatingSystem Alias
— — — — — — — — — — — — — — — — — — — — — — — — — — — -
1.0.0-rc1-update1 coreclr x64 linux
* 1.0.0-rc1-update2 coreclr x64 linux default
1.0.0-rc1-update2 mono linux/osx
  • For now, until the next Release Candidate (RC2) is available, learn the dnx and dnu commands, some of which will change to ‘dotnet’ once it becomes production ready
  • If you are familiar with NPM (the NodeJS Package Manager), you can set yourself up for quick success with Yeoman
npm install -g yo bower grunt-cli gulp
npm install -g generator-aspnet
  • Create a directory to work within
  • Navigate to that directory and run this command
yo aspnet

This fires up a command-line “wizard” which asks you about the project (the name is important — for grins call it “myfunkyapp”) you intend to build, and when complete, downloads a project template and necessary files to get you started.

Navigate to the directory for the newly created project and run the following commands:

dnu restore
dnu build
dnx web

Your asp.net project will start, using the template provided by the ‘yo aspnet’ command, and navigating to http://localhost:5000 in your browser should display a nice series of pages that describe the asp.net core environment.

From here, you can hack away. Create new Controllers, new Routes, new Views, and Models that represent your data. You can go into project.json and modify the port on which the application listens (the default is 5000), and you can modify the Startup.cs to simplify or enhance your application.

For NodeJS developers, who are familiar with Node, Express, and the addition of other libraries to their projects, editing the project.json, especially using Visual Studio, or Visual Studio Code, will allow you to bring in additional dependencies. Just don’t forget to execute ‘dnu restore’ after adding those dependencies to your project.json file. Editing the project.json is equivalent to performing ‘npm install XYZ’, which places XYZ into your node_modules directory within your project folder.

At this point, you should have a functional asp.net core MVC application, that is listening on port 5000, using a server named Kestrel. You can read about Kestrel here — https://github.com/aspnet/KestrelHttpServer

How does NGINX (pronounced Engine-X) fit into this?

First, if you are developing on Windows, you are likely going to use IIS (Internet Information Server) as your HTTP server, so this section doesn’t apply in that scenario.

But, let’s assume you are using .net core on Linux (Ubuntu, CentOS, etc). Furthermore, let’s say you have written and tested your application on port 5000, and now you want to share it with the world. You don’t really want your customer, clients, visitors to navigate to http://myfunkyapp.com:5000 do you? NO, you want them to access your great system via port 80, or port 443, like a civilized person!

NGINX, acts as a reverse proxy daemon, which listens on port 80, or port 443, and via configuration, can seamlessly redirect traffic to port 5000 to deliver your .net core based content.

Simply install NGINX via the packaging system on your flavor of Linux, or download and install it from here — http://nginx.org/

Configuration is rather easy, for a basic setup.

Let’s start with the folder /etc/nginx

Within the nginx.conf file, under the Logging Settings section, you want to edit the path to the log files to your liking.

##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

Near the bottom of the config file you should see a section for Virtual Host Configs.

##
# Virtual Host Configs
##
#This simply means include all .conf files in the conf.d folder
#same with files in the sites-enabled folder.
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

Within the /etc/nginx/conf.d folder, I created a file named reverse.conf, and the contents of this file define a server, or in my case it does. And it informs NGINX to listen on port 80, and redirect to port 5000 using the proxy_pass variable.

server { 
listen 80; # [::]:80;
listen [::]:80 ipv6only=on;
server_name myfunkyapp.com www.myfunkyapp.com *.myfunkyapp.com

access_log /var/log/nginx/reverse_access.log;
error_log /var/log/nginx/reverse_error.log debug;
#rewrite_log on;

location / {
proxy_pass http://127.0.0.1:5000;
include /etc/nginx/proxy_params;
}
}

Notice that the last include parameter in this file points to proxy_params. Let’s take a peek. I do not recall making any changes to this file. So I will include it here for reference, and you can compare it with the proxy_params file from your initial install.

proxy_redirect off; 
#default;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_buffers 32 4k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503
http_504;
#proxy_set_header Host $http_host;
#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 $scheme;

Lastly, we look at /etc/nginx/sites-enabled folder and peek into the file named default. The core piece of information you need here is the basic server definition. You simply want to insure that it is listening on port 80 for ipv4 and ipv6 on default_server, and you want server_name to be localhost. (assuming you are on the server that will host it)

server {
listen 80 default_server;
listen [::]:80 default_server;

# ipv6only=on;
root /usr/share/nginx/html;
index index.html index.htm;
# Make site accessible from http://localhost/
server_name localhost;
…..

Everything is now configured. We can test the NGINX configuration by using the command.

nginx -t

If that is successful, we can start the NGINX server.

service nginx start
#to stop nginx later simply use : service nginx stop

Navigate to http://localhost, and once again I am assuming you are on the server, and your browser should display the default NGINX page. NGINX is running, but there is nothing running on port 5000, so it delivers the default NGINX page. All is well.

Fire up our .net core application by navigating to the folder for our code, and then into the folder for the project itself, and run the following commands.

dnu restore
dnx web &

We can navigate to http://localhost again, and after a second or two of compilation, we should see the same pages we viewed on port 5000 earlier when we were developing and testing the application. Subsequent requests should scream with performance because the site is compiled at this point.

Note : when you close the terminal session, or log off of the session that you used to start the command ‘dnx web &’, the .net core application is going to stop. NGINX will continue to run, because it is running as a service. You will need to write your own bash shell script to initiate dnx web as a daemon, so that it is unaffected by your session termination.

Here is a sample bash shell script that helps you establish your DNX process as a daemon. This allows your process to continue running, even after you log off the server, or close your session:

#!/bin/sh
### BEGIN INIT INFO
# Provides: <SCRIPT_NAME>
# Required-Start: $local_fs $network $named $time $syslog
# Required-Stop: $local_fs $network $named $time $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Description: Script to run asp.net 5 application in background
### END INIT INFO
# Author: Ivan Derevianko aka druss <drussilla7@gmail.com>
WWW_USER=usernametooperateunder 
#<USERNAME>

DNXRUNTIME=/yourpath/.dnx/runtimes/dnx-coreclr-linux-x64.1.0.0-rc1-update2/bin/dnx
APPROOT=/yourpathtoyourcode
LOGROOT=/yourloggingpath/log
PIDFILE=$LOGROOT/kestrel.pid
LOGFILE=$LOGROOT/kestrel.log
# fix issue with DNX exception in case of two env vars with the same # name but different case
TMP_SAVE_runlevel_VAR=$runlevel
unset runlevel
start() {
if [ -f $PIDFILE ] && kill -0 $(cat $PIDFILE); then
echo ‘Service already running’ >&2
return 1
fi
echo ‘Starting service…’ >&2
su -c “start-stop-daemon -SbmCv -x /usr/bin/nohup -p \”$PIDFILE\” -d \”$APPROOT\” — \”$DNXRUNTIME\” web > \”$LOGFILE\”” $WWW_USER
echo 'Service Started' >&2
}
stop() {
if [ ! -f "$PIDFILE" ] || ! kill -0 $(cat "$PIDFILE"); then
echo 'Service not running' >&2
return 1
fi
echo 'Stopping Service...' >&2
start-stop-daemon -K -p "$PIDFILE"
rm -f "$PIDFILE"
echo 'Service stopped' >&2
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
echo "Usage: $0 { start | stop | restart }"
esac
export runlevel=$TMP_SAVE_runlevel_VAR

After saving your script, change execution permissions, and you can execute the script using scriptname.sh start, stop, or restart.

My Experience Has Been

Frustrating at times, primarily because I tend to complicate things. I decided to do this on Linux, on a Digital Ocean server, and had to learn NGINX. However the performance is awesome. NGINX is fast, and .net core is fast. I’ve integrated Git into my process, and have performed my updates on my Mac, and my Windows laptop using Visual Studio Code or good old fashioned vi. I push the changes to the repository on the server, and kick off a script which stops the existing dnx instance, pulls the latest from the respository, copies the pulled files to the production folder, and then restarts the dnx web process. I won’t share that here, because then you will see exactly where I am storing my files… :)

I spent about 30 days with .net core on a server on Digital Ocean. After 30 days, I decided to take it live, and turn a system developed using Release Candidate 1 of .net core, into a live, functioning web site for my company, 1 Bar Systems, Inc . The uptime has been great, the speed is great, and it is flexible enough that I can make changes along the way, and simply restart the services if necessary. With the scripts I’ve written this all happens in about 2 seconds, and the site is up and running again.

Finally, What is Fun About This?

I now have the opportunity to use my Mac to do C# development, which allows me to leverage my C# skills, while at the same time moving easily to XCode, WebStorm, or Sketch, to work on other items. It is “fun” in that it is new, and a little different. Which makes it a challenge, and challenges — can be fun!

Enjoy!

Here is another great link with some additional detail

Additional Resources: