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

C# and .NET Tips and Tricks

Quests in programming in .NET

Οκτώβριος 2011 - Δημοσιεύσεις

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 σχόλια
Δημοσίευση στην κατηγορία: , , ,
Porting a WPF application to Windows8

That is, our class implements the INotifyPropertyChanged interface and also raises the notification for the “Description” property whenever the Id or the Name properties are changed in order for the Listbox contents (databound to Description) to be refreshed. Our ViewModel for the application is as follows:

 

public class MainWindowViewModel : INotifyPropertyChanged
{
   private List<Client> _clients;
   public List<Client> Clients
   {
      get { return _clients; }
      set { _clients = value; 
         if (PropertyChanged != null) 
             PropertyChanged(this, new PropertyChangedEventArgs("Clients")); }
    }
    private Client _selectedClient;
    public Client SelectedClient
    {
       get { return _selectedClient; }
       set { _selectedClient = value; 
          if (PropertyChanged != null) 
              PropertyChanged(this, new PropertyChangedEventArgs("SelectedClient")); }
     }
     public MainWindowViewModel() {}
     public void Validate() {
        if (SelectedClient != null) {
           if (String.IsNullOrWhiteSpace(SelectedClient.Name))
              MessageBox.Show("Name cannot be empty for client");
           else
              MessageBox.Show("All ok");
        }
     }
     public event PropertyChangedEventHandler PropertyChanged;
}

 

Again, the ViewModel implementes the INotifyPropertyChanged interface and has the Clients property which will be databound to the ItemSource of the Listbox and the SelectedClient property which will be databound to the SelectedItem of the Listbox and to the detail Textboxes at the bottom of the window. Moreover, it provides the Validate method for validating the results. The XAML file describing the UI of the window is as follows:

 

<Window x:Class="AskADev.Article1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Demo WPF porting to Windows8" Height="400" Width="544" Loaded="Window_Loaded">
    <DockPanel>
        <Canvas DockPanel.Dock="Bottom" Height="100" Background="Beige">
            <TextBlock Text="Id" Canvas.Left="6" Canvas.Top="12" />
            <TextBox Text="{Binding SelectedClient.Id}" Canvas.Left="79" 
                                                        Canvas.Top="9" Width="72" />
            <TextBlock Text="Name" Canvas.Left="6" Canvas.Top="40" />
            <TextBox Text="{Binding SelectedClient.Name}" Canvas.Left="79" 
                                                          Canvas.Top="37" Width="311" />
            <Button x:Name="ButtonSubmit" Height="31" Width="73" Canvas.Left="443" 
                   Canvas.Top="63" Click="ButtonSubmit_Click">Validate</Button>
        </Canvas>
        <ListBox DockPanel.Dock="Top" ItemsSource="{Binding Clients}" 
                 SelectedItem="{Binding SelectedClient}" DisplayMemberPath="Description"/>
    </DockPanel>
</Window>

 

Where you can see the data bound properties. Finally the XAML.cs file initializes the ViewModel, attaches it to the DataContext and also provides the event handler for the button:

 

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    ViewModel = new MainWindowViewModel();
    DataContext = ViewModel;
}
private void ButtonSubmit_Click(object sender, RoutedEventArgs e)
{
   ViewModel.Validate();
}
 

We compile this and run it on a Windows7 machine and it runs correctly. Now we start the Windows8 Virtual Machine (instructions on how to install Windows8 on your desktop can be found in this post), get the executable we have created and run it on Windows8 “as is”:

image

Obviously, as expected, the application runs without any problems. But it runs on “Desktop” mode, while we want to make it run on the nice new Metro-Style UI. We open Visual Studio 2011 from within Window8 and select New Project/Templates/Visual C#/Windows Metro Style/Application:

image

The windows in this project template are more “Silverlight” like having as their root content a UserControl and not a Window. Therefore we leave the root declaration intact and copy/paste the inner contents of the original XAML.cs file. The changes we need to make are as follows (all changes are characterized as major/moderate/minor based on the time it will take you to make the changes in a full blown WPF app):

 

  • 1 (moderate) The Window must be converted to UserControl.
  • 2 (major) The “Dockpanel” is not supported (which is kinda expected since we will have a fixed size full screen application) and therefore we change it to a “Canvas” layout.
  • 3 (major) The default mode for the binding in now “One Way”. Therefore we need to specify the binding explicitly to “TwoWay”.
  • 4 (major) Textboxes get enlarged in the new “Metro” style (since they are intended for touch UIs) therefore we need to increase the spacing between the Textboxes (add approximately 10pixels). Of course if we had initially used a Stackpanel for the placement of our controls this would not be needed (good design always handles change more easily Smile)
  • 5 (minor) We need to change the colors to reflect the new style (I chose darker shades)

 

The new XAML is as follows (changes in yellow):

 

<UserControl x:Class="AskADev.Article1.Windows8.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" Loaded="Window_Loaded"
    d:DesignHeight="768" d:DesignWidth="1366">

    <Canvas>
        <Canvas Canvas.Top="668" Width="1366" Height="100">
            <TextBlock Text="Id" Canvas.Left="6" Canvas.Top="12" />
            <TextBox Text="{Binding SelectedClient.Id,Mode=TwoWay}" 
                     Canvas.Left="79" Canvas.Top="9" Width="72" />
            <TextBlock Text="Name" Canvas.Left="6" Canvas.Top="50" />
            <TextBox Text="{Binding SelectedClient.Name,Mode=TwoWay}" 
                     Canvas.Left="79" Canvas.Top="47" Width="311" />
            <Button x:Name="ButtonSubmit" Height="31" Width="73" Canvas.Left="443" 
                    Canvas.Top="63" Click="ButtonSubmit_Click">Validate</Button>
        </Canvas>
        <ListBox Background="Black" Foreground="White" Width="1366" Height="668" 
                 ItemsSource="{Binding Clients}" 
                 SelectedItem="{Binding SelectedClient,Mode=TwoWay}" 
                 DisplayMemberPath="Description"/>
    </Canvas>
</UserControl>
 

The XAML.CS file (apart from some minor changes in the name) remains the same.

Finally, the ViewModel’s file is added as is to the application. The issues that we encounter here are the following:

 

  • 6 (major) The MessageBox is not supported. Therefore you will need to find another way to inform your users about the result (probably from within a special place of the UI). In this app we will just comment it out and do nothing. In real life we would have to change the UI to have a “Messages” area.
  • 7 (minor – but annoying until you discover it) Your application will compile if you leave the INotifyPropertyChanged interface to be defined in the System.ComponentModel assembly as it is for WPF. But the binding will not work! You need to replace the “using System.ComponentModel” statement with “using Windows.Ui.Xaml.Data” which also defines the INotifyPropertyChanged interface and the bindings will magically work!

 

We recompile and run the up and we are in “Metro”!

image

 

Shout it

Posted: Τετάρτη, 19 Οκτωβρίου 2011 6:18 μμ από iwannis | 0 σχόλια
Δημοσίευση στην κατηγορία: ,