Extend Glimpse on the fly – scriptcs code execution tab in Glimpse

Β· 958 words Β· 5 minutes to read

In scriptcs, one of the things we have been paying lots of attention to recently, is the hosting story. Scriptcs CLI is simply just one of the clients using the core scriptcs libraries - which can be used to embed the rich scriptcs code parsing and execution capabilities in any app.

This weekend I put together a small Glimpse plugin which uses scriptcs hosting, and can be used for executing arbitrary code against the context of your ASP.NET application.

More after the jump.

Setting up dependencies πŸ”—

The easiest way to host scriptcs in your app is to pull ScriptCs.Hosting from Nuget. All of the heavy lifting is done inside the ScriptCs.Core however, the hosting package through some very creative design from Glenn Block, has pretty much everything wired up for you.

In our case we will embed scriptcs inside the Glimpse extension - pick up user’s code and execute it and display the output in one of the Glimpse tabs. Since we aim for ASP.NET, we will have to add Glimpse.AspNet too.

On top of that, we need to choose one of the scriptcs execution engines - in our case, the Roslyn based ScriptCs.Engine.Roslyn.

install-package scriptcs.hosting  
install-package scriptcs.engine.roslyn  
install-package glimpse.aspnet  

Hosting scriptcs πŸ”—

To host scriptcs in your app you currently (this might change later on as the APIs evolve to more friendly ones based on internal discussions and community feedback) you need to configure a Common.Logging log4net adapter and initialize scriptcs ScriptServices through the ScriptServicesBuilder.

For example:

public class ScriptCsHost  
{  
public ScriptServices Root { get; private set; }

public ScriptCsHost()  
{  
var logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);  
var commonLogger = new CodeConfigurableLog4NetLogger(logger);

var scriptServicesBuilder =  
new ScriptServicesBuilder(new ScriptConsole(), commonLogger).LogLevel(LogLevel.Info).InMemory(true).Repl(false);  
Root = scriptServicesBuilder.Build();  
}  
}  

With this, you have pretty much the entire scriptcs runtime at your fingertips. Few highlights:

    • ScriptConsole is the default output redirect - and writes to System.Console. You can send the output somewhere else if you wish. In our case we will ignore Console at all so we don’t care
    • InMemory flag forces scriptcs to operate entirely in memory and does not write any DLLs to the disk
    • Repl flag disables REPL mode
    • we chose ScriptCs.Engine.Roslyn as our execution engine - since it’s scriptcs default, we don’t need to configure it in the builder, otherwise we’d have to explicitly set it here

ScriptServicesBuilder, is extremely powerful as it allows you to configure and substitute (swap) virtually any internal piece of scriptcs. In fact, we use it in scriptcs to support the notion of Scriptcs.Modules.

Code execution with hosted scriptcs πŸ”—

Once you have a scriptcs host at your disposal, you need to go through a few steps to execute code. This code is illustrative only (some variables/classes might not make sense out of context) and full source is at GitHub:

var host = new ScriptCsHost();  
host.Root.Executor.Initialize(new[] { "System.Web" }, new[] { new GlimpseContextScriptPack(context) });  
host.Root.Executor.AddReferenceAndImportNamespaces(new[] { typeof(ITabContext), typeof(AspNetTab), typeof(ScriptCsTab), typeof(IScriptExecutor) });

var result = host.Root.Executor.ExecuteScript(code, new string[0]);  
host.Root.Executor.Terminate();  

First, we initialize ScriptExecutor by passing in assembly references (parameter one) and a list of script packs to be used by our script host. In my case I wanted to have access to System.Web and pass in my Glimpse script pack GlimpseContextScriptPack.

Next, we add more references and import namespaces based on assemblies we’d like to be available for the script - in this case things like Glimpse.Core.dll, Glimpse.AspNet.dll, ScriptCs.Contracts or the current assembly.

Finally we execute the script by passing the code to the ExecuteScript method (second parameter is an array of args to be available inside the script). After obtaining the result of the script, we terminate.

public class ScriptResult  
{  
public object ReturnValue { get; set; }  
public ExceptionDispatchInfo ExecuteExceptionInfo { get; set; }  
public ExceptionDispatchInfo CompileExceptionInfo { get; set; }  
public bool IsPendingClosingChar { get; set; }  
public char? ExpectingClosingChar { get; set; }  
}  

The three things we are interested in are:

    • ReturnValue
    • ExecuteExceptionInfo
    • CompileExceptionInfo

They will contain the result of the script or any exception - depending on when it occurred.

Using Glimpse.ScriptCs πŸ”—

I really wanted to highlight some of the key aspects of scriptcs hosting. I will not go into details of building a Glimpse plugin - but that is rather straight forward. Again, all the code is on Github.

You can get the plugin from Nuget and add to your ASP.NET solution:

install-package Glimpse.ScriptCs  

Now you’ll see a new ScriptCs tab in the Glimpse toolbar. The plugin will look for a glimpse.csx file at the root of your website (next to Global.asax).

If the code file is not found the result is simply empty:

noccode

But if it finds any C# script code in there, it will be executed - and the output displayed to you in the tab. To return a value form the script simply end the script with a line that evaluates to some value, without a semi colon at the end.

For example, this script:

var x = 1;  
x  

Will produce the following output:

code0

With the built-in Glimpse context script pack, you can also interact with the Glimpse ITabContext and access things you’d normally access when building Glimpse plugins. To do this, you have to request the *GlimpsePack* context using the familiar scriptcs *Require*.

For example:

var glimpse = Require<GlimpsePack>().Context;  
var httpContext = glimpse.GetHttpContext();

httpContext.Request.Url  

Will produce the following output (remember, the last line without semi colon is a return statement):

code

And that’s about it. This is a beta so there might be bugs here and there, but it does open some really crazy scenarios - as you can effectively write ad-hoc Glimpse extensions for yourself.

In the next version of scriptcs (when this PR is merged) we will allow interaction with the current in memory web app assembly too!

Any feedback is welcome, and happy script(cs)ing.

About


Hi! I'm Filip W., a software 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