Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια

C# and .NET Tips and Tricks

Quests in programming in .NET

Παρουσίαση με Ετικέτες

Όλες οι Ετικέτε... » Scripting   (RSS)
A scripting language for your (web)app with DLR or Roslyn

Well, this subject always excited me! The fact that you can provide at your executable, a programming language (script) that the user can use to interact with your application is something amazing. In this post, we see how this is possible by using the Dynamic Language Runtime (DLR) and IronPython, and then see whether the services provided by the Roslyn project can help as achieve the same result with C#.

The whole idea behind providing a scripting language at runtime may find the following three applications:

 

  • You implement a DSL (Domain Specific Language) meaning that you want to provide to your end use an expressive tool to specify his behavior in a specific application domain. In this case you expose some parts of the information held in your application in the form of methods that the user can use in his scripts to achieve the desired result.
  • You want to be able to change a specific behavior without having to recompile the whole application or in the case where you use the same executable in multi-tenant scenarios.
  • You want your application’s users to have some fun by interacting with your application in ways you have never imagined.

 

We will use a typical ASP.NET MVC application for our tests. The web app consists of one Controller with the following View:

image

That is the user can write a script in the TextArea (in IronPython in this figure) and then click the “Execute” button. The server receives the code and executes it returning the result just below the “Execution” header. While the For-Loop is native to IronPython, the PrintOnWebPage statement has been added by our WebApp providing a way for the script writer to write HTML for the view.

 

Implementation with the DLR (Dynamic Language Runtime)

Download and compile the DLR from Codeplex. Then in your ASP.NET MVC project add the following references:

 

  • IronPython.dll
  • Microsoft.Dynamic.dll
  • Microsoft.Scripting.dll

 

You will find those in the Bin folder of the DLR project you have compiled in the previous step. The whole idea behind script execution is shown in the figure below:

image

An object is initialized called the Script Engine. This engine will receive the user’s script and handle its execution. The engine without anything else only recognizes the native “Python” commands and therefore it has very limited interaction with our WebApp. To change this we need to provide “libraries” in the Scope object used by the engine which contain methods that the user’s script can use. In the initial figure’s example the PrintOnWebPage method is such a method. To provide it to our user the following steps have been taken:

 

A BasicLib class has been implemented:

 

public class BasicLib
{
    public ViewDataDictionary ViewData { get; set; }

    public void PrintOnWebPage(string Content)
    {
        if (ViewData["Result"] == null)
            ViewData["Result"] = Content;
        else
            ViewData["Result"] = ViewData["Result"].ToString() + Content;

    }
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

Note that the class has a property ViewData. PrinOnWebPage uses this ViewData property to add the HTML content provided at its parameters. An object of this class, will be instantiated, its ViewData property will be given the value of the actual View’s ViewData and then it will be added to the engine’s scope with a special name (eg. BasicLib). From that point on, the PrintOnWebPage method is available to the script via the following notation: BasicLib.PrintOnWebPage.

So the whole picture is as follows. The script engine with its scope are implemented in a class like the following:

 

public class ScriptManager
{
    private ScriptEngine _engine;
    private ScriptScope _scope;

    public ScriptManager(){
        _engine = Python.CreateEngine();
        _scope = _engine.CreateScope();
    }
    public void AddLibrary(object Lib){
        _scope.SetVariable(Lib.GetType().Name,Lib);
    }
    public string RunCode(string script){
        try {_engine.Execute(script, _scope);}
        catch (Exception e) {return e.Message;}
        return "";
    }
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

The class provides a “RunCode” method where the script in the string parameter will be executed and an “AddLibraryMethod” that adds a library (like the BasicLib) to the scope. The SetVariable method that adds the library to the scope receives as second parameter the name that will be used to recognize the library in our scripts. In this case we just use the library’s type name.

 

Having this ScriptManager our Controller receives the script from the view and performs the following:

 

[HttpPost]
[ValidateInput(false)] 
public ActionResult Index(String Code)
{
    ...
    BasicLib basicLib = new BasicLib();
    basicLib.ViewData = ViewData;
    ScriptManager Mngr = new ScriptManager();
    Mngr.AddLibrary(basicLib);
    Mngr.RunCode(Code);

    return View();
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

And this is all it takes to provide a scripting language to your user’s. Not bad with so few lines of code don’t you agree?

 

Implementation with the Roslyn CTP

If you have found the previous approach exciting wait until you see this one. Roslyn is the project that puts the C# compiler at your service! It is an API that gives you access to its inner workings. This enables you to do lots of interesting stuff, one of which being the fact that you can give your users the option to use C# as their scripting language.

First you need to download and install the VS 2010 SDK and the Roslyn CTP. Then you need to add the following references to your ASP.NET MVC Application:

 

  • Roslyn.Compilers
  • Roslyn.Compilers.CSharp

 

The idea behind executing scripts written in C# is more or less the same as it is for the DLR. We again have a script engine:

 

Roslyn.Scripting.CSharp.ScriptEngine Engine = new Roslyn.Scripting.CSharp.ScriptEngine(new[] {
                           "System", this.GetType().Assembly.Location});

 

And we provide a session which is a single object whose public methods and properties will be accessible by our script. In our case:

 

BasicLib basicLib = new BasicLib();
basicLib.ViewData = ViewData;
Roslyn.Scripting.Session session = Roslyn.Scripting.Session.Create(basicLib);

 

The complete handling of C# scripting code in the Controller’s post action is given below:

 

Roslyn.Scripting.CSharp.ScriptEngine Engine = new Roslyn.Scripting.CSharp.ScriptEngine(new[] {
                           "System", this.GetType().Assembly.Location});
BasicLib basicLib = new BasicLib();
basicLib.ViewData = ViewData;
Roslyn.Scripting.Session session = Roslyn.Scripting.Session.Create(basicLib);
Engine.Execute(Code, session);
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

And this concludes our brief introduction into providing a scripting language to your users. The post’s project can be downloaded

Posted: Δευτέρα, 31 Οκτωβρίου 2011 1:14 μμ από iwannis | 0 σχόλια
Δημοσίευση στην κατηγορία: , , ,