Key Takeaways
- ChakraBridge enables C# developers to utilize JavaScript frameworks within C# applications by leveraging the Chakra JavaScript engine from Microsoft Edge.
- The integration does not support HTML or CSS directly, meaning JavaScript frameworks that do not rely on DOM or CSS are suitable for use with ChakraBridge.
- Developers can debug JavaScript code within C# applications using Visual Studio, although they must navigate through executed code in debug mode via the Script Documents section.
- ChakraBridge provides additional JavaScript tools like XMLHttpRequest and setTimeout, which are typically available in web browsers, to enhance functionality within C# applications.
- The project is open for further community contributions and enhancements, potentially expanding to support more JavaScript features and frameworks in the future.
How to Use It?
This is pretty simple: just head to https://github.com/deltakosh/JsBridge and clone the project to your hard drive. Now you have two options: you can either add the ChakraBridge project (which is a WinRT library) in your solution or you can reference ChakraBridge.winmd from /dist folder.Initializing Chakra
Once referenced, you can call these lines of code to get Chakra ready to use:host = new ChakraHost();
The variable named host
is your JavaScript context.
You may also want to be able to trace the messages sent to the JavaScript console. To do so, please add this code:
Console.OnLog += Console_OnLog;
Once connected, this event handler will be called everytime the JavaScript code executes “console.log()
“.
Which JavaScript frameworks can I use?
Before defining what you can do, you have to understand that Chakra is a JavaScript engine which means that you can execute JavaScript code in your app but there is nothing related to HTML or CSS. You can then pick any framework not related to HTML (DOM operations) or CSS. Here are some examples (but there are MANY MANY more): Once you’ve picked the framework that you want to use, you have to inject it into your Chakra context. In my case I wanted to use CDC (CloudDataConnector) because I needed a way to seamlessly connect to various cloud data providers (Amazon, Azure, CouchDB, etc..). You can either download the .js files and embed them in your project or download them every time you launch your application:await ReadAndExecute("cdc.js");
await ReadAndExecute("azuremobileservices.js");
await ReadAndExecute("cdc-azuremobileservices.js");
await ReadAndExecute("sample.js");
You can replace ReadAndExecute
by DownloadAndExecute
if you prefer referencing live .js files
Now your JavaScript context has compiled and executed the referenced files.
Please note that “sample.js” is a custom JavaScript file which contains the client code for my application:
varCDCAzureMobileService = new CloudDataConnector.AzureDataService();
varCDCService = new CloudDataConnector.DataService(new CloudDataConnector.OfflineService(), new CloudDataConnector.ConnectivityService());
CDCAzureMobileService.addSource('https://angularpeoplev2.azure-mobile.net/', 'xxxxxxx', ['people']);
CDCService.addSource(CDCAzureMobileService);
vardataContext = {};
varonUpdateDataContext = function (data) {
if(data &&data.length) {
syncPeople(data);
}
}
varsyncPeople = function (data) {
sendToHost(JSON.stringify(data), "People[]");
}
CDCService.connect(function (results) {
if (results === false) {
console.log("CDCService must first be successfully initialized");
} else {
console.log("CDCService is good to go!");
}
}, dataContext, onUpdateDataContext, 3);
Nothing fancy here, I’m just using CDC to connect to an Azure mobile service in order to get a list of people.
Getting data back from JavaScript world
Next, I’ll get my data back from the JavaScript context. As you may have seen in the “sample.js” file, when the data context is updated, I’m calling a global function calledsendToHost
. This function is provided by ChakraBridge to allow you to communicate with the C# host.
To get it working, you have to define what types can be sent from JavaScript:
CommunicationManager.RegisterType(typeof(People[]));
So now when sendToHost
is called from JavaScript context, a specific event will be raised on C# side:
CommunicationManager.OnObjectReceived = (data) =>
{
varpeopleList = (People[])data;
peopleCollection = new ObservableCollection<People>(peopleList);
peopleCollection.CollectionChanged += PeopleCollection_CollectionChanged;
GridView.ItemsSource = peopleCollection;
WaitGrid.Visibility = Visibility.Collapsed;
};
Obviously you are responsible for the mapping between your JavaScript object and your C# type (same properties names).
Calling JavaScript functions
On the other hand you may want to call specific functions in your JavaScript context from your C# code. Think, for instance, about committing a transaction or adding a new object. So first let’s create a function for a specific task in our “sample.js” file:
commitFunction = function () {
CDCService.commit(function () {
console.log('Commit successful');
}, function (e) {
console.log('Error during commit');
});
}
To call this function from C#, you can use this code:
host.CallFunction("commitFunction");
If your function accepts parameters, you can pass them as well:
host.CallFunction("deleteFunction", people.Id);
The current version of ChakraBridge accepts int
, double
, bool
and string
types.
Debugging in the JavaScript context
Thanks to Visual Studio, it is still possible to debug your JavaScript code even if you are now in a C# application. To do so, you first have to enable script debugging in the project properties: Then, you can set a breakpoint in your JavaScript code. But there is a trick to know: You cannot set this breakpoint in the files in your project as they are here just as a source. You have to reach the executed code through the Script Documents part of the Solution Explorer when running in debug mode:How does it work?
Interop
Let’s now discuss how things work under the hood. Basically, Chakra is based on a Win32 library located at “C:\Windows\System32\Chakra.dll” on every Windows 10 desktop devices. So the idea here is to provide a internal C# class that will embed all entry points to the DLL throughDllImport
attributes:
internal static class Native
{
[DllImport("Chakra.dll")]
internal static extern JavaScriptErrorCodeJsCreateRuntime(JavaScriptRuntimeAttributesattributes,
JavaScriptThreadServiceCallbackthreadService, out JavaScriptRuntimeruntime);
[DllImport("Chakra.dll")]
internal static extern JavaScriptErrorCodeJsCollectGarbage(JavaScriptRuntimehandle);
[DllImport("Chakra.dll")]
internal static extern JavaScriptErrorCodeJsDisposeRuntime(JavaScriptRuntimehandle);
The list of available functions is pretty long. ChakraBridge is here to encapsulate these functions and provide a higher level abstraction.
Other option to consider here: you can also use Rob Paveza’s great wrapper called js-rtwinrt: https://github.com/robpaveza/jsrt-winrt. It’s higher-level than the pure Chakra engine and it avoids needing P/Invoke.
Providing missing pieces
One important point to understand is that Chakra only provides the JavaScript engine. But you, as the host, have to provide tools used alongside JavaScript. These tools are usually provided by browsers (think about C# without .NET). For instance,XmlHttpRequest
object or setTimeout
function are not part of JavaScript language. They are tools used BY the JavaScript language in the context of your browser.
To allow you to use JavaScript frameworks, ChakraBridge provides some of these tools.
This is an ongoing process and more tools will be added to ChakraBridge in the future by me or the community.
Let’s now have a look at the implementation of XmlHttpRequest
:
using System;
using System.Collections.Generic;
using System.Net.Http;
namespace ChakraBridge {
public delegate void XHREventHandler();
public sealed class XMLHttpRequest {
readonlyDictionary<string, string> headers = new Dictionary<string, string>();
Uri uri;
string httpMethod;
private int_readyState;
public intreadyState {
get{
return _readyState;
}
private set {
_readyState = value;
try {
onreadystatechange?.Invoke();
}
catch {}
}
}
public string response =>responseText;
public string responseText {
get;
private set;
}
public string responseType {
get;
private set;
}
public bool withCredentials { get; set; }
public XHREventHandleronreadystatechange { get; set; }
public void setRequestHeader(string key, string value) {
headers[key] = value;
}
public string getResponseHeader(string key) {
if(headers.ContainsKey(key)) {
return headers[key];
}
return null;
}
public void open(string method, string url) {
httpMethod = method;
uri = new Uri(url);
readyState = 1;
}
public void send(string data) {
SendAsync(data);
}
async void SendAsync(string data) {
using(varhttpClient = new HttpClient()) {
foreach(varheader in headers) {
if(header.Key.StartsWith("Content")) {
continue;
}
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
readyState = 2;
HttpResponseMessageresponseMessage = null;
switch (httpMethod) {
case "DELETE":
responseMessage = await httpClient.DeleteAsync(uri);
break;
case "PATCH":
case "POST":
responseMessage = await httpClient.PostAsync(uri, new StringContent(data));
break;
case "GET":
responseMessage = await httpClient.GetAsync(uri);
break;
}
if(responseMessage != null) {
using (responseMessage) {
using (varcontent = responseMessage.Content) {
responseType = "text";
responseText = await content.ReadAsStringAsync();
readyState = 4;
}
}
}
}
}
}
}
As you can see, the XmlHttpRequest
class uses internally a HttpClient
and uses it to mimic the XmlHttpRequest
object that you can find in a browser or in node.js
.
This class is then projected (literally) to the JavaScript context:
Native.JsProjectWinRTNamespace("ChakraBridge");
Actually, the entire namespace is projected as there is no way to project only a single class. So a JavaScript is then executed to move the XmlHttpRequest
object to the global object:
RunScript("XMLHttpRequest = ChakraBridge.XMLHttpRequest;");
Handling garbage collections
One of the pitfalls you may face if you decide to extend ChakraBridge is garbage collection. Indeed, the JavaScript garbage collector has no idea of what is happening outside of its own context. So for instance, let’s see how thesetTimeout
function is developed:
internal static class SetTimeout {
public static JavaScriptValueSetTimeoutJavaScriptNativeFunction(JavaScriptValuecallee, bool isConstructCall,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] JavaScriptValue[] arguments,
ushortargumentCount, IntPtrcallbackData) {
// setTimeout signature is (callback, after)
JavaScriptValuecallbackValue = arguments[1];
JavaScriptValueafterValue = arguments[2].ConvertToNumber();
varafter = Math.Max(afterValue.ToDouble(), 1);
uintrefCount;
Native.JsAddRef(callbackValue, out refCount);
Native.JsAddRef(callee, out refCount);
ExecuteAsync((int)after, callbackValue, callee);
return JavaScriptValue.True;
}
staticasync void ExecuteAsync(intdelay, JavaScriptValuecallbackValue, JavaScriptValuecallee) {
await Task.Delay(delay);
callbackValue.CallFunction(callee);
uintrefCount;
Native.JsRelease(callbackValue, out refCount);
Native.JsRelease(callee, out refCount);
}
}
SetTimeoutJavaScriptNativeFunction
is the method that will be projected inside JavaScript context. You can note that every parameter is gathered as a JavaScriptValue
and then cast to the expected value. For the callback function (callbackValue)
, we have to indicate to JavaScript garbage collector that we hold a reference so it could not free this variable even if no one is holding it inside JavaScript context:
Native.JsAddRef(callbackValue, out refCount);
The reference has to be released once the callback is called:
Native.JsRelease(callbackValue, out refCount);
On the other hand, C# garbage collector has no idea of what is happening inside the Chakra black box. So you have to take care of keeping reference to objects or functions that you project into the JavaScript context. In the specific case of setTimeout
implementation, you first have to create a static field that point to your C# method just to keep a reference on it.
Why not use a Webview?
This is a valid question that you may ask. Using only Chakra provides some great advantages:- Memory footprint: No need to embed HTML and CSS engines as we already have XAML.
- Performance: We can directly control JavaScript context and, for instance, call JavaScript function without having to go through a complex process like with the webview.
- Simplicity: The webview needs to navigate to a page to execute JavaScript. There is no straightforward way to just execute JavaScript code.
- Control: By providing our own tools (like
XHR
orsetTimeout
), we have a high level of granularity to control what JavaScript can do.
Going Further
Thanks to Chakra engine, this is the beginning of a great collaboration between C#, XAML and JavaScript. Depending on the community response, I plan to add more features in the ChakraBridge project to be able to handle more JavaScript frameworks (for instance, it could be great to add support for canvas drawing in order to be able to use all awesome charting frameworks available for JavaScript). If you are interested in reading more about Chakra itself you can go to the official Chakra samples repository: https://github.com/Microsoft/Chakra-Samples. You may also find these links interesting:More hands-on with Web Development
This article is part of the web development series from Microsoft tech evangelists on practical JavaScript learning, open source projects, and interoperability best practices including Microsoft Edge browser and the new EdgeHTML rendering engine. We encourage you to test across browsers and devices including Microsoft Edge – the default browser for Windows 10 – with free tools on dev.modern.IE:- Scan your site for out-of-date libraries, layout issues, and accessibility
- Download free virtual machines for Mac, Linux, and Windows
- Check Web Platform status across browsers including the Microsoft Edge roadmap
- Remotely test for Microsoft Edge on your own device
- Coding Lab on GitHub: Cross-browser testing and best practices
- Microsoft Edge Web Summit 2015 (from our engineering team and JS community)
- Woah, I can test Edge & IE on a Mac & Linux! (from Rey Bango)
- Advancing JavaScript without Breaking the Web (from Christian Heilmann)
- The Edge Rendering Engine that makes the Web just work (from Jacob Rossi)
- Unleash 3D rendering with WebGL (from David Catuhe)
- Hosted web apps and web platform innovations (from Kevin Hill and Kiril Seksenov)
- vorlon.JS (cross-device remote JavaScript testing)
- manifoldJS (deploy cross-platform hosted web apps)
- babylonJS (3D graphics made easy)
Frequently Asked Questions about Using JavaScript Frameworks Inside C# with ChakraBridge
How can I install ChakraBridge in my C# project?
To install ChakraBridge in your C# project, you need to use the NuGet package manager. Open your project in Visual Studio, go to the ‘Tools’ menu, select ‘NuGet Package Manager’, and then ‘Manage NuGet Packages for Solution’. In the search bar, type ‘ChakraBridge’ and install it. Remember to add the reference to your project after installation.
Can I use other JavaScript frameworks with ChakraBridge?
Yes, ChakraBridge allows you to use any JavaScript framework that can run in a browser. This includes popular frameworks like React, Angular, and Vue.js. You just need to include the JavaScript file of the framework in your project and reference it in your code.
How can I debug JavaScript code in ChakraBridge?
Debugging JavaScript code in ChakraBridge can be a bit tricky as it doesn’t provide a built-in debugger. However, you can use console.log statements for debugging. The output of these statements can be captured in C# using the ‘JavaScriptConsole’ class provided by ChakraBridge.
Is it possible to use ChakraBridge in a WinForms application?
Yes, ChakraBridge can be used in a WinForms application. You can create a new instance of the ‘JavaScriptRuntime’ class and execute JavaScript code using the ‘RunScript’ method. The result can be displayed in a WinForms control.
How can I handle JavaScript errors in ChakraBridge?
ChakraBridge provides the ‘JavaScriptError’ class to handle JavaScript errors. You can catch these errors in a try-catch block in your C# code. The ‘JavaScriptError’ class provides information about the error, including the error message and the line number where the error occurred.
Can I use ChakraBridge to run Node.js code?
No, ChakraBridge is designed to run JavaScript code in a browser-like environment. It doesn’t support Node.js specific features like file system access or network requests. However, you can use Edge.js if you need to run Node.js code in a C# application.
How can I call a C# method from JavaScript in ChakraBridge?
ChakraBridge allows you to expose C# methods to JavaScript. You can do this by creating a JavaScript function that calls a C# method using the ‘JavaScriptNativeFunction’ class. This function can then be called from your JavaScript code.
Is ChakraBridge compatible with .NET Core?
No, ChakraBridge is not compatible with .NET Core. It is a .NET Framework library and requires the full .NET Framework to run. If you need to run JavaScript in a .NET Core application, you can use Jint or ClearScript.
Can I use ChakraBridge in a WPF application?
Yes, ChakraBridge can be used in a WPF application. The process is similar to using it in a WinForms application. You can execute JavaScript code and display the result in a WPF control.
How can I update the version of JavaScript used by ChakraBridge?
ChakraBridge uses the Chakra JavaScript engine provided by Microsoft. The version of JavaScript used by ChakraBridge depends on the version of the Chakra engine installed on your system. To update the version of JavaScript, you need to update the Chakra engine.
David Catuhe is a Principal Program Manager at Microsoft focusing on web development. He is author of the babylon.js framework for building 3D games with HTML5 and WebGL. Read his blog on MSDN or follow him on Twitter.