From TypeScript to C# Scripting and Back Again

by Michael Szul on

No ads, no tracking, and no data collection. Enjoy this article? Buy us a ☕.

As many of you know, I've been working diligently to finalize a beta release for the Metron JavaScript framework. I'm happy to say that it's officially in Bower and NPM at the moment--no more need to install via GitHub.

If you've perused the Metronical GitHub org, you'll see a couple of projects there--not just Metron. What gives?

The metron-cli is something that I've been working on for quite some time. Metron itself is a combination of a JavaScript convenience library that I wrote 6-7 years ago, as well as the patterns and practices that I had developed for CRUD operations on the front-end. On top of that, I have a great deal of experience building/working with code generators from when I worked at Barbella Digital. For my current job, I built a quick-and-dirty Windows Form application for the generation of Bootstrap-enabled front-end HTML, Entity models, and ASP.NET Web API services. This was a somewhat watered down version of what I was used to working with, since I had limited time at work to really flesh it out. When I decided to undertake a rewrite of the Metron library, I also decided that I would take those previous generator skills from my past decade of experience working with code generators, and try to build a command line utility that worked as a generator, scaffolding tool, and publisher.

The trick, of course, is picking the right technology for the job. A lot of my past XSLT work was in C#, and .NET does have a pretty good transformation utility (even if it doesn't support XSLT 2.0). To create a C# executable, have the user install it, and then make sure it has the correct permissions, introduces more friction than say, a global NodeJS utility (think Grunt or Yeoman), but was there really a need to choose between one or the other?

EdgeJS is a bridge between .NET and NodeJS that allows for data and execution to be passed back and forth. I decided that the best option was to create the shell (and the publisher) as a NodeJS command line utility that can be globally installed, but then use EdgeJS to pass data to a .NET library for the scaffolding and code generation (in order to use the XSLT libraries--both .NET's and Saxon's).

What does that look like in practice?

First, I had to make a decision on how to use EdgeJS. You can put C# code directly in JavaScript files. You can also call a DLL directly. I decided to go with C# scripting, which is something that's been available (but not used nearly enough) since Roslyn was launched.

Here's the TypeScript code that calls the CSX script:

export function scaffold(config: any): void {
          var args = {
               step: "scaffold"
             , datasource: config["generator.datasource"]
             , database: config["generator.database"]
             , projectname: config["project.name"]
          }
          var mScaffold = edge.func(path.join(shelljs.pwd().toString(), "lib", "Startup.csx"));
          mScaffold(args);
          console.log(mScaffold);
      }
      

The call to edge.func does the heavy lifting--creating an EdgeJS bridge to the .NET framework through the Startup.csx C# scripting file. The next line executes the bridge, while passing in the JSON object.

The CSX, meanwhile, has a couple of conventions that need to be followed. First, referencing an external library that isn't in the GAC is done through an #r directive:

#r ".\space.metron.transform\bin\Debug\Space.Metron.Transform.dll"
      

Next, the EdgeJS bridge expects the CSX file to have a class named Startup, and an async method inside of that called Invoke that accepts the arguments:

public async Task<object> Invoke(dynamic input)
      {
          ...
      }
      

You can view the entire Startup.csx file on GitHub. You'll notice that the Invoke method takes a argument of dynamic. This is so you can access the JSON object that was passed from NodeJS. This is different from a standard CSX script where you might not have an actual class object, and you access the command line arguments via the Args object.

What does this give me? It allows me to call a C# script from NodeJS, where I can access the Metron transformation DLL to pass in the appropriate arguments, write to the console, and generate out the code I need (or scaffold the application shell). I can even pass data back to NodeJS and the TypeScript code if I need to inform the calling application of anything.

The metron-cli is a work is progress, but it's already successfully scaffolding, getting database tables, publishing files based on a master page, and generating out code while in a debug/development environment. I hope to get a release candidate out there within the next month.