Writing node.js applications in C#

Tags: saltarelle-compiler, c#, javascript, node.js

Node.js has come from nothing to being one of the most watched projects on GitHub incredibly quickly. and I think that by today everyone knows, and has an opinion, it. Personally, I find it interesting and I really like the simplicity it provides. But what I don't like is that I have to code my application in Javascript in order to use it. I have tried it, and being a .net developer used to Visual Studio with Resharper, I was frustrated every single minute because none of the IDEs I tried (and I think I tried all the major Javascript IDEs) was able to give me IntelliSense good enough to help more than it hurt.

If you are like me, you probably wish that you can write a Node.js application in C#. Say, for example, that there were a tool that would allow you to write, in a .cs file in Visual Studio, with (working) IntelliSense at every point, a program like this:

using NodeJS.HttpModule;

class Program {
    static void Main() {
        Http.CreateServer((req, res) => {
            res.Write("Hello, world");
            res.End();
        }).Listen(8000);
    }
}

and translate it into something like:

var http = require('http');
http.createServer(function(req, res) {
    res.write('Hello, world');
    res.end();
}).listen(8000);

I don't know about such zero-overhead tool, but there is a tool which will transform it to

require('mscorlib');
var http = require('http');
////////////////////////////////////////////////////////////////////////////////
// Program
var $$Program = function() {
};
$$Program.$main = function() {
    http.createServer(function(req, res) {
        res.write('Hello, world');
        res.end();
    }).listen(8000);
};
Type.registerClass(null, '$Program', $$Program, Object);
$$Program.$main();

The Saltarelle C# to Javascript compiler now has support for Node.js. You probably also feel that the new C#5.0 keywords async and await would be a good fit for Node.js programs. So do I, and many of the Node.js APIs have counterparts that return a Task, which can be awaited.

A Simple File Browser

To demonstrate the capabilities of Saltarelle with Node.js, here is a tutorial wich creates a fully asynchronous (on the server) file browser by writing the source code in C#.

Creating the project

First we need to create a project for our application. Create a new Visual Studio project of type Console Application and call it SimpleNodeJSFileBrowser. Then open the package manager console and

> Install-Package Saltarelle.NodeJS

Now unload the project and reload it again (we need to do this because of a Visual Studio issue when replacing the standard mscorlib with a custom one), and remove the attributes it complains about when you try to compile. Now, in order to make the project run, and work, when we hit Ctrl+F5 we need to do a few more things:

  1. Download the correct node executable to the project folder.
  2. The Script generated by Saltarelle requires a module called 'mscorlib' to be included. This module is available with the Saltarelle.Runtime package (which was installed as a dependency when we installed Saltarelle.NodeJS). Therefore, create a directory called node_modules in the project directory. Unload the project from the solution explorer and add the following code to the BeforeBuild target:
      <Target Name="BeforeBuild">
        <ItemGroup>
          <MscorlibScript Include="$(SolutionDir)packages\**\mscorlib.js"/>
        </ItemGroup>
        <Copy SourceFiles="@(MscorlibScript)" DestinationFolder="$(ProjectDir)node_modules"/>
      </Target>
    
    This will ensure that whenever we update the Saltarelle.Runtime package, the correct mscorlib.js file will be copied to our node_modules directory so it can be found when we run our project.
  3. In the project properties, on the Debug tab enter the following values:
    • Start action: Start external program "C:\Windows\System32\cmd.exe"
    • Command line arguments: /K "C:\<path to your project>\node.exe" SimpleNodeJSFileBrowser.js
    The reason to do it like this rather than put node.exe directly in the "Start external program" option is that by doing it in the above way, the console window will prevail in case of an error that terminates the node process. Unfortunately, msbuild properties such as $(ProjectName) do not work in these options.
  4. Test that everything works by pasting the simple program above into the Program.cs file, hit Ctrl+F5 and navigate to http://localhost:8000. The browser should display "Hello, world".

Adding the file browser code

Now that we have a working skeleton, it is time for us to create the actual file browser that we want. This code will use the C# 5.0 features 'async' and 'await', so you need Visual Studio 2012 to compile this (the RC works). Now it is time to actually type in our program, line by line (no, you won't do this because you will use editor features, but this is for explanation; Additionally, the syntax highlighter I use does unfortunately not support indenting a whole block of code so you have to pretend that the indentation is correct:

using System;
using System.Threading.Tasks;
using NodeJS.FSModule;
using NodeJS.HttpModule;
using NodeJS.PathModule;
using NodeJS.UrlModule;

All the different node modules reside in their own namespaces. Adding a using directive does not cause the module to be require()d, that only happens when you use something from a module.

namespace SimpleNodeJSFileBrowser {
    public class Program {
        public static void Main() {
            var server = Http.CreateServer(async (req, res) => {
                try {
                    var url = Url.Parse(req.Url);
                    var path = url.Path;
                    if (path.StartsWith("/"))
                        path = path.Substr(1);
                    if (path.EndsWith("/"))
                        path = path.Substr(0, path.Length - 1);
                    
                    var physicalPath = "C:\\" + string.DecodeUri(Path.Normalize(path.Replace("/", Path.Sep)));
                    
                    res.Write("<html><body>");

This is just some boring code to ensure that the path neither starts nor ends with a slash, and to convert the URL to a physical path. Of course, if you want to browse something that is not C:\, you can change this.

                    string[] files;
                    try {
                        files = await FS.ReaddirTask(physicalPath);
                    }
                    catch (AggregateException ex) {
                        res.Write("Error reading directory:<br>");
                        foreach (var m in ex.InnerExceptions) {
                            res.Write(m.Message + "<br>");
                        }
                        res.Write("</body></html>");
                        return;
                    }

This is where it gets interesting. The FS.ReaddirTask call will do some magic to transform the call to a Node.js "fs.readdir()". Awaiting the task will return either the list of files, or throw an exception if the node call failed (meaning that the callback was invoked with a non-null error argument). If this happens, we just print the error.

                    if (files.Length == 0) {
                        res.Write("The directory is empty");
                        res.Write("</body></html>");
                        return;
                    }
                    
                    Task<Stats>[] statTasks = files.Map(f => FS.StatTask(physicalPath + "\\" + f));
                    try {
                        await Task.WhenAll(statTasks);
                    }
                    catch (AggregateException) {
                    }

This code uses Javascript's Array.map method to start tasks to invoke the node stat() method on each file we previously found. Note that all these tasks will be executed in parallell. Then we wait for all the tasks to finish with an "await Task.WhenAll(...)". This await will throw an AggregateException if any of the involved tasks failed. We ignore this exception because we will check the outcome of each individual task later. 

                    res.Write("<ul>");
                    
                    if (path != "") {
                        res.Write("<li><a href=\"/" + Path.Normalize(path + Path.Sep + "..") + "\">..</a></li>");
                    }
                    
                    for (int i = 0; i < files.Length; i++) {
                        res.Write("<li>");
                        if (statTasks[i].IsFaulted) {
                            res.Write(files[i] + ": Error retrieving stats: " + statTasks[i].Exception.InnerExceptions[0].Message);
                        }
                        else {
                            var stats = statTasks[i].Result;
                            if (stats.IsDirectory()) {
                                var newPath = "/" + (path != "" ? path + "/" : "") + files[i];
                                res.Write("<a href=\"" + newPath + "\">" + files[i] + "</a>");
                            }
                            else {
                                res.Write(files[i] + ": " + statTasks[i].Result.Size + "B");
                            }
                        }
                        res.Write("</li>");
                    }
                    
                    res.Write("</ul></body></html>");

Again, this code is rather boring. It just loops over each of the tasks and writes different strings depending on whether the task was successful, whether the item is a directory, etc.

                }
                finally {
                    res.End();
                }
            });
            server.Listen(8000);
        }
    }
}

This finally block will ensure that whenever an exception is thrown, the res.end() method will be invoked. It does not matter whether this is due to normal completion, an unhandled exception, or a failing task. Finally (no pun intended), we tell the server to listen to port 8000, just as you would write in Javascript directly.

A look at the generated code

Warning: This is not for the faint hearted, the generated code can look quite indimidating at first. Bear in mind, though, that we are invoking an asynchronous method, using the result to dispatch a bunch of parallell asynchronous calls and waiting for them all to finish before we process the result. If you did not use Saltarelle, this would get you into the mess of nested callbacks, or using one of the many control flow libraries available, which can also be a pain to debug and step into. I'm not saying the generated code is "better" than the alternatives, but I don't think it's far worse. And the source C# is quite nice.

The generated file is in the project output directory (by default bin\Debug) and called SimpleNodeJSFileBrowser.js. Open it and investigate. I will not go through the entire contents here, but I will make a few comments:

  • The compiler knows which modules we require and inserts the appropriate calls to import them.
  • The entire body of our async lambda has been replaced with a nested function called $sm. The only real operations performed by the method is to set the initial state and invoke this inner function.
  • Whenever there is an await, it is replaced with code like
    $state = NEW_VALUE;
    $t1.continueWith($sm);
    $doFinally = false;
    return;
    
    This code will cause the state machine to continue from its current position as soon as the awaited task finishes. It also ensures that any finally blocks are not run when awaiting a task.
  • The FS.ReaddirTask (and FS.StatTask) calls are replaced with code like ss.Task.fromNode(fs, 'readdir', physicalPath). This is the magic call that will invoke the given method with a set of arguments, and will add a callback that does the right things.
  • Code blocks that do not contain any awaits are translated quite literally.

Final Words

This code is available in the Saltarelle Compiler Samples.

The (C# source) code, although it works, is quite messy. In the next post we will refactor it so the actual directory lookup will become a Node.js module.

And, of course, if you prefer nested callbacks over the auto-generated state machine, there is of course metadata for those methods as well, so you can choose the style you prefer.

7 Comments

  • Lucian said

    Why bother running this on node, when you can just use mono?

    As for when you have to run JS, try TypeScript.

  • Tom Theisen said

    Mike:

    In general I prefer c# to javascript for a variety of reasons. Some of them include: the ability (but not requirement) to use statically checked types, actually working foreach loops for sequences, generators (yield), lazy evaluated sequences (with linq), and fully functional intellisense in a development environment.

    I like javascript too, but I think it's silly to call C# "ugly".

  • Julien said

    "This is just some boring code to ensure that the path neither starts nor ends with a slash, " -> actually all you have to write is path = url.Path.Trim('/'); :)

  • Michael J. Ryan said

    I have to agree with some of the above comments... It really seems like an alien solution to the issue, if you mainly want additional intellisense. I would consider using the recent TypeScript release if this is the path you really want to go down.

Add a Comment