Using JavaScript Frameworks inside C# with ChakraBridge

By David Catuhe

This article is part of a web development series from Microsoft. Thank you for supporting the partners who make SitePoint possible.

JavaScript has, without doubt, the most vibrant ecosystem out there. There are gazillions of new frameworks released every month (https://www.javascripting.com/).

A new javascript framework is coming

As a C# developer – even with a great, active C# community – you may sometimes find yourself a little bit jealous.

What if we could bring the JavaScript language and ecosystem also into the C# world? What if a C# developer could use JavaScript inside C#?

Fret not! I’m thrilled to announce a new WinRT project I’ve created – ChakraBridge – which will allow you to get invited to the party like any web developers.

ChakraBridge

Indeed, thanks to Chakra (the JavaScript engine used by Microsoft Edge), it is now possible to host one of the fastest JavaScript engines (and also the one with the highest support of ECMAScript 6) inside any Universal Windows Platform application. ChakraBridge embeds Chakra engine in a WinRT application and provides all required high level tools to use it seamlessly in a C# / UWP application.

People developing a HTML/JS/CSS based UWP App (WWA or hosted app in the old world) don’t need to host Chakra separately as it’s already a part of the sandbox.

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 called sendToHost. 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:

Debugging JavaScript

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:

Debugging JavaScript

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 through DllImport 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 the setTimeout 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 or setTimeout), 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:

More in-depth learning from our engineers and evangelists:

Our community open source projects:

More free tools and back-end web dev stuff:

  • http://SalaryNet30.com margaret col

    Like Antonio responded I am taken by
    surprise that a stay at home mom able to make $5849 in a few weeks on the
    internet. have a peek at this website on my profIle

    -==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=–=-=-=-=-=–=-=-=-=-=-=–=-=-=–=-=-=-=-=-=-=-=-=–=-=-=-=-=–=

    =sssssssss

  • serware

    I’m a C# programmer because I hate Javascript ¿Is there a way to use directly EdgeHtml to manipulate DOM llike we did with MSHTML?

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Front-end, once a week, for free.