Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C#

Practical example of adding C# scripting support to an application

Rate me:
Please Sign up or sign in to vote.
4.60/5 (17 votes)
23 May 2017CPOL5 min read 43.8K   1K   31   6
Adding C# sripting support to an application for providing a way for customizing the application or integrating with other applications.

Why we need scripting support in our applications?

There are two main reasons for adding for scripting support to an application; Customization and integration.

Customization: Customizing the behaviour of the application. Custom validation rules, adding custom steps to a workflow, creating custom reports...

Integration: Integrating our applications with other applications. Importing files generated by other applications, exporting the application's data to different formats, translating the message between the applications...

Which language is better for scripting?

Actually there is no common answer to this question. My personal idea is choosing a platform and a language that has easy to use and you are comfortable will be better.

There are many widely used scripting languages like VBA, Python, Boo and Pascal. But you do not need to use them because they are widely used.

Shouldn't I worry about the users that will write the scripts?

Most of the time "no." Because 99.9% of the cases, your users or customers will not be the person who will write the scripts. They will just contact with you, and someone from your team will write the scripts for them and customer will pay for it. So, selecting a language that your team already use and comfortable with and has tools for scripting support will be a better choice.

Steps of implementing the scripting support

To add scripting support, you only need to complete three simple steps.

1- Write the script

The script is no more than some text. So, you can write it with a simple text editor like Notepad or Notepad++. You can also create a simple class in Microsoft Visual Studio then import its code to your application.

Having a code editor for writing scripts in your application may be a good feature but not required. If it is hard for you to find a good script editor that you can easily embed to your application, just provide a way to import script files or text content  to your application.

Following is a simple script that gets a collection of Person objects and writes thier information to a single comma separated file.

C#
using System;
using System.IO;
using System.Collections.Generic;
using System.Windows.Forms;
using ScriptingSampleApp.Core;

namespace ScriptingSampleApp.Scripts
{
    class ScriptClass
    {
        public void ExportPersonList(IList<Person> personList)
        {
            string _filePath = askFileName();

            if (String.IsNullOrEmpty(_filePath))
                return;

            using (StreamWriter _writer = new StreamWriter(_filePath))
            {
                for (int i = 0; i < personList.Count; i++)
                {
                    _writer.WriteLine(personList[i].Name + "," + personList[i].Surname + "," + personList[i].Age);
                }
            }
        }

        private string askFileName()
        {
            //Pops up Save File Dialog and returns the entered file path
            //....
        }
    }
}

As you can see, your script should be structured as a complete C# code file. It should contain necessary using statements, namespace, class and method declerations.

Consider putting all the shared classes that will be accessed from the scripts to a separate DLL file and reference it in your scripts. For example, sample script uses the Person class under the namespace ScriptingSampleApp.Core from the dll file ScriptingSampleApp.Core.dll. The next step shows how to reference these additional dll files.

2- Compile the script

The next step is compiling the script. Compiling the script has following sub steps;

  1. Create a C# code compiler
  2. Prepare compiler parameters
  3. Compile the script either from a string or providing the script files' paths
  4. Check if the compilation succeeded or generated any error.
  5. Get the reference to the compiled assembly

Following sample code block demonstrates these steps.

C#
//1- Create code compiler for C# language
CodeDomProvider _codeDomProvider = CodeDomProvider.CreateProvider("CSharp");

//2- Fill compiler parameters
CompilerParameters _compilerParameters = new CompilerParameters();
_compilerParameters.ReferencedAssemblies.Add("system.dll");
_compilerParameters.ReferencedAssemblies.Add("system.windows.forms.dll");
_compilerParameters.ReferencedAssemblies.Add("ScriptingSampleApp.Core.dll");

_compilerParameters.GenerateExecutable = false;
_compilerParameters.GenerateInMemory = true;

//3- Compile the script
CompilerResults _compilerResults = _codeDomProvider.CompileAssemblyFromSource(_compilerParameters, m_txtScript.Text);

//4- Check if compilation has errors
if (_compilerResults.Errors.HasErrors)
{
    string _errorMessage = "Compilation failed.\r\n";
    foreach (string _error in _compilerResults.Output)
    {
        _errorMessage += _error + "\r\n";
    }

    MessageBox.Show(_errorMessage);
    return;
}

//5- Get the generated assembly
Assembly _compiledAssembly = _compilerResults.CompiledAssembly;

At this step, do not forget to add all the system and additional assemblies that your script uses, to the ReferencedAssemblies collection of the CompilerParameters you prepared. This sample script references the system.dll and system.windows.forms.dll which come with .Net Framework and it also references additional ScriptingSampleApp.Core.dll which contains the Person class.

3- Invoke the methods in the compiled assembly

Now the script is compiled to an assembly you can create instances of the classes defined in the assembly and invoke its methods. This step requires using reflection.

Technics for using the compiled script assembly, are the same as using dynamically loaded plugins. You must decide the way your code finds and invokes the operations in the scripts.

There are too many ways of deciding which classes to create and which methods to invoke in the compiled assembly.

For deciding which class(es) to instantiate, you may;

  • Predefine or set the rules for the class names. For example, class name must be "ScriptClass" or class names must start with "ExportScriptClass_". It is up to you.
  • Add custom attributes to the classes that can contains the invokable methods. You may define a custom attribute like [ExportScriptClassAttribute] and add this attribute to every class in the script that contains the invokable methods.
  • Define base interfaces or classes and derive your classes in the scripts from them. For example, define an interface like IExportScript and implement this interface in your script class.

Deciding which method or methods to call within the script you may use the same technics as deciding for classes to instantiate.

Following sample code requires that the script contains a single class named ScriptClass under the namespace ScriptingSampleApp.Scripts and has a method named ExportPersonList. It creates an instance of that class, gets the required methods MethodInvoke and invokes the method with required parameters.

C#
Assembly _compiledAssembly = _compilerResults.CompiledAssembly;

//Create an instace of the class that contains the script method
var _customScriptClassInstance = _compiledAssembly.CreateInstance("ScriptingSampleApp.Scripts.ScriptClass");

//Get the Type info for the script class
var _typeInfo = _customScriptClassInstance.GetType();
//Get the method info that contains the script codes
var _methodInfo = _typeInfo.GetMethod("ExportPersonList");
//Invoke the method with the required parameters (pass null as the second parameter if method has no parameters)
var oReturnValue = _methodInfo.Invoke(_customScriptClassInstance, new object[] { m_PersonList });

 

Using the sample code

Sample code contains two porjects; one WinForms application and a class library project.

ScriptingSampleApp: Contains a single form that has a data grid filled with sample data, a large multi-line TextBox for scripts and three button for importing, saving and executing the scripts. Sample scripts are under the "Scripts" folder in this project.

ScriptingSampleApp.Core: This project is for demonstrating the shared class libraries that contains shared classes. In this case it only contains one class Person which the script needs to use.

Image 1

To try the sample code, first click the "Load Script From File" button and select one of the sample script files. Then, click the "Export using this script" button and enter the file name that will be generated.

More

You probably do not compile the scripts every time your application starts. So you will need to save the compiled assemblies to disk or to database and then reload the previously compiled assembly when your application starts. Keeping extra information about the script like its name, function, type... may also be needed.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Freelance
Sweden Sweden
Experienced senior C# developer, sometimes codes in Java and C++ also. He designs and codes desktop applications (WinForms, WPF), windows services and Web APIs (WCF, ASP.Net MVC). He is currently improving his ASP.Net MVC and JavaScript capabilities. He acts as an architect, coder and trainer.
He is mostly experienced in Media Asset Management (MAM) applications, broadcasting sector and medical applications.

LinkedIn: www.linkedin.com/mustafa-kok/
GitHub: github.com/nthdeveloper
StackOverflow: stackoverflow.com/users/1844220/nthdeveloper

Comments and Discussions

 
GeneralMy vote of 1 Pin
2Busy24731-Dec-19 7:27
2Busy24731-Dec-19 7:27 
QuestionMy vote of #4 Pin
BillWoodruff24-May-17 16:44
professionalBillWoodruff24-May-17 16:44 
AnswerRe: My vote of #4 Pin
Mustafa Kok24-May-17 19:48
professionalMustafa Kok24-May-17 19:48 
Hi Bill, thank you for the comments.
As you said, this sample is actually about compiling and using some C# code at runtime. It may not be as simple as writing a script and executing it with a script engine, because you are doing the script engine's work yourself. Below statement is from Python scripting documentation;
Quote:
Python scripts have to be processed by another program called the Python interpreter. The interpreter reads your script, compiles it into bytecodes, and then executes the bytecodes to run your program.

Actually it is the same as loading and running plugins as I mentioned in the 3rd section of the article. I omitted, executing the compiled code using common interfaces and using different AppDomains for security and re-load issues. Because adding these to the article would make it more complicated. I only wanted to show core concepts of scripting, "writing, compiling and executing". Another article may be written that covers the whole scenario.
M.Kok

GeneralRe: My vote of #4 Pin
BillWoodruff25-May-17 2:53
professionalBillWoodruff25-May-17 2:53 
QuestionCongratulation, A diffrent perspective Pin
M. Salih Karagöz24-May-17 10:37
M. Salih Karagöz24-May-17 10:37 
AnswerRe: Congratulation, A diffrent perspective Pin
Mustafa Kok24-May-17 22:34
professionalMustafa Kok24-May-17 22:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.