Scripting Web API self host with Roslyn CTP – Part 1

Β· 1606 words Β· 8 minutes to read

If you follow me on Twitter you probably already know that recently I’ve been playing around with Roslyn. If you don’t know about Roslyn, in very short, you can think of it as “compiler as a service”.

There is a whole plethora of stuff that Roslyn allows us to do, one of the coolest being the scripting API - enabling us to use C# as a script language (think i.e. Perl or Python). So I had this idea, why not script a fully functional web server through Web API self host?

More after the jump.

Some background πŸ”—

I will not go into any details about Roslyn, there are some great resources on the Internet that do just that. But in order to proceed with the code shown here you need to install the latest (September) Roslyn CTP, available for download from the Roslyn homepage. You will also need Visual Studio 2012 to use the C# interactive window. But bear in mind, VS is not necessary to script with Roslyn!

I gotta thank Shay Friedman for getting me interested in Roslyn, as it’s a terrific project which, once shipped, will very likely drastically change the landscape of the C# world. Kudos to Kevin Pilch-Bisson, Kirill Osenkov and the entire team for terrific work. There are already some great services out there on the web utilizing Roslyn, most notably the excellent Compilify.net by Justin Rusbatch.

What are we going to do? πŸ”—

The plan is to utilize the Scripting API of Roslyn to script a usable Web API self host server.
I will split this post into two parts, as we will explore three routes:

  • In part 1, we will be using C# script files (CSX) and running them through a Roslyn tool called RCSI
  • Also in part 1, we will go on and use C# interactive window, running code directly from a new console window in Visual Studio 2012
  • In part 2 we will build a demo console app which acts as a scripting console where user can type code manually - we will allow the user to script the server from there. Kind of your own tiny version of Compilify.net

Web API via C# script files (CSX) πŸ”—

You can think of C# script files as any script files from any language. You import some DLLs you wanna use, then you import the namespaces and then you just write code. Simple as that.

You don’t need Visual Studio to script CSX files, any text editor would do. It’s a bit easier with VS because if you have Roslyn CTP installed, the moment you create a CSX file, you’ll get IntelliSense support.

With that said, go to VS, choose File > New > General > Visual C# Script.

Now that we have new script we are ready to go. First we need to bring in all the required Web API DLLs. You do that by adding this on top:

#r "C:Program Files (x86)Microsoft ASP.NETASP.NET MVC 4AssembliesSystem.Web.Http.SelfHost.dll"  
#r "C:Program Files (x86)Microsoft ASP.NETASP.NET MVC 4PackagesNewtonsoft.Json.4.5.6libnet40Newtonsoft.Json.dll"  
#r "C:Program Files (x86)Microsoft ASP.NETASP.NET MVC 4AssembliesSystem.Web.Http.dll"  
#r "C:Program Files (x86)Microsoft ASP.NETASP.NET MVC 4AssembliesSystem.Net.Http.Formatting.dll"  
#r "C:Program Files (x86)Microsoft ASP.NETASP.NET MVC 4AssembliesSystem.Net.Http.WebRequest.dll"  
#r "C:Program Files (x86)Microsoft ASP.NETASP.NET MVC 4AssembliesSystem.Net.Http.dll"

using System;  
using System.IO;  
using System.Web.Http;  
using System.Web.Http.SelfHost;  

Note that you might need to modify the paths to suit your system - these are simply the default locations of the DLLs if you have ASP.NET MVC 4 installed.

Next, we just write the script necessary to create a web server:

var address = "http://localhost:579";  
var conf = new HttpSelfHostConfiguration(new Uri(address));  
conf.Routes.MapHttpRoute(name: "DefaultApi",  
routeTemplate: "api/{controller}/{id}",  
defaults: new { id = RouteParameter.Optional }  
);

var server = new HttpSelfHostServer(conf);  
server.OpenAsync().Wait();  
Console.ReadKey();  

If you used self host Web API, this is nothing special - typical code needed to spin out a Web API self host server. That’s about it -I saved the script as webApiScript.csx. Now we need to locate RCSI, which is what’s needed to run the CSX scripts. Normally it would be here: C:Program Files (x86)Microsoft Roslyn CTPBinaries.

You should now run RCSI from command line accordingly:

> rcsi [path\_to\_my_CSX]  

Now here is a little quirk I ran into. The path references to the imported DLLs didn’t get resolved property, and instead of proper locations, RCSI was trying to import the DLLs from the same directory it was in. I contacted Kevin Pilch-Bisson about that because it seems like a bug (?). Anyway, to work around that, for the time being I just dumped all the required DLLs + RCSI into one folder:

OK, let’s run it (remember, CMD needs elevated priviliges, since Web API self host needs elevated priviliges) - in my case it will look like this:

> rcsi "c:UsersFilipDocumentsVisual Studio 2012ProjectsRoslyn.WebApiRoslyn.WebApiwebApiScript.csx"  

You will notice it will “hang”, since we put Console.ReadKey at the end - that’s on purpose. The moment you exit the execution context of the script, the server would be shut down. Now we can navigate in the browser to http://localhost:579/ (as we scripted this address) and we get a response from the server:

This indicates the server is alive, and all we have is a little script file to show for!

Of course this is hardly usable yet, since there are no controllers - so let’s tackle that. It’s a bit more complicated since Web API and it’s System.Web.Http.Dispatcher.IAssembliesResolver expects the controllers to be in some assembly, so we cannot just straight ahead dump an ApiController class into the CSX script.

Instead, let’s take advantage of Roslyn Compiler services - import the additional following DLLs and namespaces into the CSX:

#r "c:UsersFilipdocumentsvisual studio 2012ProjectsRoslyn.WebApipackagesRoslyn.Compilers.Common.1.2.20906.2libnet45Roslyn.Compilers.dll"  
#r "c:usersfilipdocumentsvisual studio 2012ProjectsRoslyn.WebApipackagesRoslyn.Compilers.CSharp.1.2.20906.2libnet45Roslyn.Compilers.CSharp.dll"

using System.IO;  
using System.Reflection;  
using Roslyn.Compilers.CSharp;  
using Roslyn.Compilers;  

What we’ll do is create a dynamic assembly in runtime, and parse the CSharp controller’s code on the fly there (how f*cking cool is that ?!).

Let’s add our controller code as string, and instruct the Roslyn compiler to parse it as a syntax tree (notice, in the previous versions of Roslyn you might have been using ParseCompilationUnit, that’s gone in the latest CTP, it’s ParseText now):

var text = @"  
using System.Web.Http;  
public class TestController : ApiController  
{  
public string Get() {  
return ""Hello World"";  
} 

public string Get(int id) {  
return ""Hello ""+id;  
}  
}";  
var tree = SyntaxTree.ParseText(text);  

Next, let’s create a Compilation (dynamically linked library), emit an assembly and load into memory:

var compiledCode = Compilation.Create(  
"controllers.dll",  
options: new CompilationOptions(outputKind: OutputKind.DynamicallyLinkedLibrary),  
syntaxTrees: new[] { tree },  
references: new[] { new MetadataFileReference(typeof(object).Assembly.Location), new MetadataFileReference(@"C:Program Files (x86)Microsoft ASP.NETASP.NET MVC 4AssembliesSystem.Web.Http.dll") });

Assembly assembly;  
using (var stream = new MemoryStream())  
{  
EmitResult compileResult = compiledCode.Emit(stream);  
assembly = Assembly.Load(stream.GetBuffer());  
}  

Since Web API uses all AppDomain.CurrentDomain assemblies to look for controllers, the controller can now be found. If we run it again:

Touchdown!

Parsing code from external file πŸ”—

You don’t have to keep the controllers code inline with the rest of the script - you can seperate it into a different file. Let’s add a text (!) file called webapiScriptControllers.txt and add the original controller, plus one more controller, there:

using System.Web.Http;

public class TestController : ApiController  
{  
public string Get() {  
return "Hello World";  
} 

public string Get(int id) {  
return "Hello "+id;  
}  
};

public class DifferentController : ApiController  
{  
public string[] Get()  
{  
return new[] { "Hello World", "I'm a different controller" };  
}

public string Get(int id)  
{  
return "Different " + id;  
}  
};  

Let’s go back to our CSX file and delete the inline string declaration of the controllers and change the SyntaxTree.ParseText method to ParseFile:

//var tree = SyntaxTree.ParseText(text); //commented out  
var tree = SyntaxTree.ParseFile(@"c:UsersFilipDocumentsVisual Studio 2012ProjectsRoslyn.WebApiRoslyn.WebApiwebapiScriptControllers.txt");  

You can run the server again using RCSI and here is what happens - everything still works and we get the new controller (DifferentController) we just introduced:

Content negotiation obviously works as well:

Using C# interactive window πŸ”—

As a final thing today, let’s have a look at C# interactive window.

Once you have the Roslyn CTP installed, it will add the aforementioned scripting window to your Visual Studio under View > Other Windows > C# Interactive.

The Interactive window is powered by the new C# language service and allows us to execute pretty much any code you’d execute with RCSI. You can copy our CSX script there and hit ENTER, or, even better - highlight the entire (or chunks if you wish) of the script in the CSX, right click and choose the option Execute in interactive (tip: you may want to omit the last line, Console.ReadKey)

This does effectively the same thing as RCSI did and we can now browse our Web API from the browser, since the web server is up and running.

Summary and source πŸ”—

This is the end of part 1. We will continue soon with part two (as soon as I find time to write the blog, the code is already done πŸ™‚ ). I hope I managed to at least intrigue you with the Roslyn project, because it’s, to say the least, really cool. Scripting API is just a part of it (although we touched the Roslyn Compiler services as well), and Roslyn is shaping up to be a tremendous addition to the .NET family.

I tweeted this yesterday, but let me reiterate it here - for a static language like C#, to be able to walk up to a text editor, i.e. Notepad, script a web server (self hosted Web API is a web server after all) and execute it almost as if it was a Perl or Python script is really mind boggling - I love this.

I’ll leave you with this thought - till next time, part 2 soon!

About


Hi! I'm Filip W., a cloud architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP