One of the most promising perspectives in scriptcs, in my opinion, is hosting. Basically hosting is the complete package of scriptcs, just put inside your own application. It can be pretty mind blowing to think about this. Hosting makes it possible execute C# scripts inside of any standard .Net application.

With my scriptcs.rebus project, I wanted to add a feature, which would use scriptcs.hosting for executing scripts inside of a host. The script was supposed to be send over either MSMQ, RabbitMQ, or Azure Service Bus. When the host received the script it would be executed. It should also be possible to added NuGet references, local dependencies, and import namespaces to be used by the script.

I had read the excellent blog post by Filip Wojcieszyn on extending Glimpse using scriptcs. This blog post gives a small hint on the perspectives for scriptcs hosting. When I came up the idea of combining scriptcs hosting with messaging, I had a Jabbr session with Glenn Block, who guided me in the right direction.

The end of the session with Glenn, was that all I needed to do was reference scriptcs.hosting, and I should be given all the dependencies needed.

Initial Attempt

I started looking into how the internals of scriptcs is working, and how hosting in particular is working. I soon realized that there is a number of different dependencies at work, and quickly came up with idea of using AutoFac, which is also used inside of scriptcs hosting. I wrote a rather nice script module, on which I registered all my dependencies from scriptcs.hosting.

This script module would be registered in my ScriptHandler. The ScriptHandler subscribes to messages of type Script:

Copy to Clipboard

So upon receiving a script, the script module is registered, and I can resolve the logger and ScriptExecutor. I thought this was a pretty cool setup.

For completeness my ScriptExecutor would look like this:

Copy to Clipboard

The Execute() method will take the script to be executed and a number NuGet dependencies. Setting the Environment.CurrentDirectory is required for NuGet to be able to determine which packages are already installed, and where to download and install the new ones. The PreparePacakages() method will download and install NuGet packages, if they are not already installed. If there is any script packs installed, they’ll be referenced, using the ScriptPackResolver. The ScriptExecutor is initialized with the NuGet references and the script packs. ExecuteScript() is called on the ScriptExecutor. The result is verified and logged if relevant.

This is solution that I released in version 0.4.0. Despite missing a few essential scriptcs.hosting features, like importing namespaces and reference local dependencies, I decided to release it anyway.

On of the key features of the upcoming 0.5.0 release is Mono support. I’d like to add the options to use the Mono compiler inside of the hosting pieces. During this work I ran into an issue with the way I wired up my dependencies. It was rather trivial to set an option for using the Mono compiler, and then replace it with the dependency to Roslyn. It worked so far that I could compile plain C#, just like the Roslyn compiler, but if I wanted to compile C# with some features that is not supported by Roslyn yet, like dynamic or async/await, the Mono compiler would throw me an error. At first I was convinced that this must be a scriptcs bug, and there called out for help in the scriptcs community. Once again, I got in contact with Glenn Block. So, one morning, we toke a one-to-one on Skype, were I showed Glenn my code and put him into my mindset of what I’d like to achieve. Glenn is a nice guy, so he said, this cool, but you’re doing it wrong. So with Glenn looking me over my shoulder, I re-implemented the Execute() of my ScriptExecutor.

Actual Approach

Glenn told me that all my wiring using AutoFac wasn’t necessary, there is already a built-in helper class for that. It’s called ScriptServicesBuilder. What ScriptServicesBuilder does is wire up the dependencies for you. It then serves you a ScriptServices instance, which makes it trivial to override the dependencies.

I therefore added a small method to my ScriptExecutor class, which I borrowed some pieces from here:

Copy to Clipboard

I do some configuration of the console and the logger. The log level is set depending on whether the user would like some debugging output. Whether the user would like to use the Mono compiler, I override the ScriptEngine. Finally, I override the logical file system, which scriptcs uses as its environment. I override it with this:

Copy to Clipboard

All this customization does is that it tells scriptcs that my dependencies are located in the same folder as the script is executed. Otherwise, I’d have to copy all my dependencies over to a \bin folder.

So after some iterations my Execute() method, ended up like this:

Copy to Clipboard

I get the scriptcs dependencies wired up and served with CreateScriptServices(). I pull out the ScriptExecutor and the ScriptPackResolver. One, important step, is to initialize the InstallationProvider. In my case, the InstallationProvider is a NuGetInstallationProvider. If you forget to initialize you are given a ArgumentNullException if you try to download a NuGet package with the PackageInstaller. I still have my PreparePackages() which looks like this. The functionality is the same, I just added some more logging and error handling.

I then initialize the ScriptExecutor with the path to the assemblies, and the resolved script packs. I import additional namespaces and adds a reference to some local assemblies if necessary.

Finally, I call ExecuteScript() on the ScriptExecutor. If we’re using logging, the result, errors or exceptions are sent to the console.

My Execute() method now holds a lot more code, than in my initial attempt, but on the other hand I can save a bunch of code and complexity with this solution.

This, final solution, is how it is implemented in the upcoming scriptcs.rebus 0.5.0.

Recent Posts

Tags