Exposing .NET Types to JavaScript
Relevant source files
The following files were used as context for generating this wiki page:The Jurassic engine provides powerful mechanisms for interoperability, allowing .NET code to be exposed to and manipulated by the JavaScript runtime. This enables developers to extend the JavaScript environment with custom functionality, expose .NET libraries, and create a tight integration between the two environments. The core of this functionality revolves around wrapping .NET types, instances, and delegates into JavaScript-compatible ObjectInstance derivatives.
There are several ways to expose .NET functionality, ranging from simple delegate-based functions to complex class hierarchies. The primary methods involve using the ScriptEngine to set global variables to instances of wrapper classes like ClrFunction, ClrInstanceWrapper, or custom classes that inherit from ObjectInstance. A security feature, EnableExposedClrTypes, can be used to control whether CLR types can be exposed, which is useful for sandboxing script execution.
Sources: Jurassic/Core/ScriptEngine.cs:814-823, README.md:5-9
Core Mechanisms for Exposing .NET Code
Jurassic offers several strategies for making .NET code available to JavaScript. The choice of method depends on the complexity and nature of the .NET code being exposed.
Exposing .NET Functions
The most direct way to expose a .NET method is by wrapping it in a Delegate and using ScriptEngine.SetGlobalFunction(). This method creates a ClrFunction instance, which is a JavaScript function that calls the underlying .NET delegate.
Sources: Jurassic/Core/ScriptEngine.cs:1155-1164
// From Unit Tests/Core/DocumentationSamples.cs
[TestMethod]
public void CallANETMethod1()
{
var engine = new ScriptEngine();
engine.SetGlobalFunction("test", new Func<int, int, int>((a, b) => a + b));
Assert.AreEqual(11, engine.Evaluate<int>("test(5, 6)"));
}
Sources: Unit Tests/Core/DocumentationSamples.cs:51-57
The ClrFunction class is a specialized FunctionInstance that wraps a .NET Delegate. When called from JavaScript, it handles the type conversion of arguments from JavaScript types to the expected .NET types, invokes the delegate, and converts the return value back to a JavaScript-compatible type.
This diagram shows the process of exposing and calling a .NET function.
Exposing Custom JavaScript Objects from .NET
For more complex scenarios, you can create custom JavaScript objects by defining a .NET class that inherits from Jurassic.Library.ObjectInstance. This approach gives you full control over the object's properties and methods as they appear in JavaScript.
Defining Properties and Methods
You can define properties and methods in the constructor of your ObjectInstance subclass using DefineProperty(). For methods, you can use attributes like [JSFunction] and [JSConstructorFunction] to automatically expose .NET methods.
The following example defines a custom AppInfo object with name and version properties.
// From Unit Tests/Core/DocumentationSamples.cs
public class AppInfo : ObjectInstance
{
public AppInfo(ScriptEngine engine)
: base(engine)
{
this.DefineProperty("name", new PropertyDescriptor("Test Application", PropertyAttributes.Sealed), true);
this.DefineProperty("version", new PropertyDescriptor(5, PropertyAttributes.Sealed), true);
}
}
[TestMethod]
public void ExposingANETClass1()
{
var engine = new ScriptEngine();
engine.SetGlobalValue("appInfo", new AppInfo(engine));
Assert.AreEqual("Test Application 5", engine.Evaluate<string>("appInfo.name + ' ' + appInfo.version"));
}
Sources: Unit Tests/Core/DocumentationSamples.cs:64-81
Using Attributes for Methods
The [JSFunction] attribute marks a .NET method to be exposed as a JavaScript function on the object. The PopulateFunctions() method must be called in the constructor to register these attributed methods.
// From Unit Tests/Core/DocumentationSamples.cs
public class Math2 : ObjectInstance
{
public Math2(ScriptEngine engine)
: base(engine)
{
this.PopulateFunctions();
}
[JSFunction(Name = "log10")]
public static double Log10(double num)
{
return Math.Log10(num);
}
}
[TestMethod]
public void ExposingANETClass2()
{
var engine = new ScriptEngine();
engine.SetGlobalValue("math2", new Math2(engine));
Assert.AreEqual(3.0, engine.Evaluate<double>("math2.log10(1000)"));
}
Sources: Unit Tests/Core/DocumentationSamples.cs:83-104
The build process includes a code generation step that finds these attributes and creates efficient stub methods to handle the interoperability layer, avoiding reflection overhead at runtime for each call. This process is managed by the Attribute Code Generation project.
Sources: Attribute Code Generation/Program.cs
Exposing .NET Instances and Static Types
Jurassic can also wrap arbitrary .NET objects and static types, making their public members accessible from JavaScript. This is done through ClrInstanceWrapper and ClrStaticTypeWrapper.
This class diagram illustrates the key wrapper classes and their relationship.
ClrInstanceWrapper
This class wraps an instance of any .NET object. When a property is accessed or a method is called on the wrapper from JavaScript, it uses reflection to perform the corresponding operation on the wrapped .NET object.
You can wrap an instance and set it on the script engine using SetGlobalValue.
// From Jurassic/Core/ScriptEngine.cs
if ((value is ObjectInstance) == false)
value = new ClrInstanceWrapper(this, value);
// ...
this.Global.SetPropertyValue(variableName, value, this.Global, throwOnError: true);
Sources: Jurassic/Core/ScriptEngine.cs:1126-1129
When a property is accessed, ClrInstanceWrapper.GetPropertyValue() is called, which in turn finds and invokes the appropriate property getter or method on the wrapped object.
Sources: Jurassic/Library/ClrWrapper/ClrInstanceWrapper.cs
ClrStaticTypeWrapper
This class wraps a .NET Type object, exposing its static fields, properties, and methods to JavaScript. This is useful for providing access to utility classes (like System.Math) or enum values.
Like instance wrappers, you can expose a static type via SetGlobalValue.
// From Jurassic/Core/ScriptEngine.cs
if (value is Type)
value = ClrStaticTypeWrapper.FromCache(this, (Type)value);
Sources: Jurassic/Core/ScriptEngine.cs:1124-1125
ClrStaticTypeWrapper.GetPropertyValue() handles requests for static members, using reflection to find and access them on the wrapped Type.
Sources: Jurassic/Library/ClrWrapper/ClrStaticTypeWrapper.cs
Security Considerations
The ability to expose CLR types provides immense power but also introduces security risks if scripts from untrusted sources are executed. An attacker could potentially use reflection to access sensitive parts of the .NET environment.
To mitigate this, Jurassic provides the EnableExposedClrTypes property on the ScriptEngine. When set to false, it prevents new CLR types from being instantiated and exposed to the script engine, effectively sandboxing the script from arbitrary .NET access.
/// <summary>
/// Gets or sets whether CLR types can be exposed directly to the script engine. If this is set to
/// <c>false</c>, attempting to instantiate CLR types from script may result in exceptions being
/// thrown in script.
/// </summary>
public bool EnableExposedClrTypes
{
get;
set;
}
Sources: Jurassic/Core/ScriptEngine.cs:814-823
Summary of Exposure Methods
| Method | Description | Key Classes/Methods | Use Case |
|---|---|---|---|
| Expose .NET Function | Wraps a .NET Delegate as a global JavaScript function. | ScriptEngine.SetGlobalFunction(), ClrFunction | Exposing simple, stateless utility functions. |
| Custom JS Object | Create a .NET class inheriting from ObjectInstance to define a custom JavaScript object. | ObjectInstance, [JSFunction], PopulateFunctions() | Building rich, stateful JavaScript objects with well-defined interfaces in .NET. |
| Wrap .NET Instance | Expose an existing instance of any .NET class to JavaScript. | ScriptEngine.SetGlobalValue(), ClrInstanceWrapper | Making an instance of a .NET object available for manipulation in script. |
| Wrap Static Class | Expose the static members (methods, properties, fields) of a .NET class. | ScriptEngine.SetGlobalValue(), ClrStaticTypeWrapper | Providing access to .NET utility classes (e.g., Math) or enums. |
Sources: Jurassic/Core/ScriptEngine.cs, Jurassic/Library/Function/ClrFunction.cs, Unit Tests/Core/DocumentationSamples.cs, Jurassic/Library/ClrWrapper/ClrInstanceWrapper.cs, Jurassic/Library/ClrWrapper/ClrStaticTypeWrapper.cs
Conclusion
Jurassic's interoperability layer is a cornerstone of its design, offering a flexible and efficient bridge between the .NET and JavaScript worlds. By providing multiple mechanisms for exposing .NET code—from simple functions with ClrFunction to complex object models with ObjectInstance subclasses and automatic wrapping via ClrInstanceWrapper and ClrStaticTypeWrapper—it empowers developers to build deeply integrated applications. The use of attribute-based code generation further enhances performance by minimizing runtime reflection, making the interaction between the two environments both powerful and fast.