I am working on a small project that will primarily be a C# library. I also want to create a JavaScript counterpart that is functionally equivalent. I would like to be able to test both from the same unit tests.
To be clear I am NOT going to be using this in a production environment where C# calls JS, or vice versa. I only want to blend C# and JavaScript in the context of the unit tests so I can verify and maintain cross-platform compatibility. Most examples I find show that this is possible within Blazor using JSRuntime, but I don't want to do this within a web application.
Below is an example of what I am trying to accomplish.
C# Method:
public class CsharpLibrary
{
public static bool CheckLength(string str) {
if (str.Length > 10)
return false;
return true;
}
}
JavaScript Function:
function CheckLength(str) {
if (str.length > 10)
return false;
return true;
}
Unit Test
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CsharpJavascriptUnitTesting
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void CheckLength_TooLong()
{
var str = "1234567890123456789";
var isValid = CsharpLibrary.CheckLength(str);
Assert.IsFalse(isValid);
// TODO: is it possible to load the local Javascript file?
// TODO: call JavaScript CheckLength(str);
// TODO: assert that the value returned is also false
}
}
}
Thanks to the recommendation in the comment above from Collen I was able to accomplish this with the Jurassic library.
[TestMethod]
public void CheckLength_TooLong()
{
var str = "1234567890123456789";
var isValid = CsharpLibrary.CheckLength(str);
Assert.IsFalse(isValid);
var engine = new Jurassic.ScriptEngine();
engine.ExecuteFile(#"..\..\JsLibrary.js");
isValid = engine.CallGlobalFunction<bool>("CheckLength", str);
Assert.IsFalse(isValid);
}
Related
I'm making my backend on C# and frontend on JS.
I want to reuse some alghoritms written in C# in browser JavaScript.
Simplified example:
class Fibonacci {
int Fib(int x) {
if (x == 0) return 0;
int prev = 0;
int next = 1;
for (int i = 1; i < x; i++)
{
int sum = prev + next;
prev = next;
next = sum;
}
return next;
}
Is it possible to compile one library-independent class to WebAssembly and use it from browser? How?
Yes, with NativeAOT-LLVM (https://github.com/dotnet/runtimelab/tree/feature/NativeAOT-LLVM). There's a similar question with a full answer at Compiling C# project to WebAssembly
From what I understand, C# needs some middleware / framework to convert it to wasm. If your use case does not warrant Blazor, you might try the UNO platform: https://github.com/unoplatform/Uno.Wasm.Bootstrap
You can call .NET methods form JavaScript functions like this
Here is the razor html
#inject IJSRuntime JS
<button #onclick=#CallToJS >Call JS function</button>
Here is the code behind
#code {
/// <summary>
/// This method calls javascript function with .net object reference parameter
/// </summary>
/// <returns>Awaitable task</returns>
private async Task CallToJS() => await JS.InvokeVoidAsync("sayHi", DotNetObjectReference.Create(this));
/// <summary>
/// This method is called from javascript
/// </summary>
/// <param name="name">some string parameter coming from javascript</param>
/// <returns>Returns string</returns>
[JSInvokable]
public async Task<string> CalledFromJS(string name)
{
// Here you can call your class library
return $"Hi {name}";
}
}
And this is the JavaScript code calling .NET method
function sayHi(dotNetHelper) {
var promise = dotNetHelper.invokeMethodAsync('CalledFromJS', "Surinder");
promise.then(function (result) {
console.log(result);
});
}
Please have a look at the JavaScript code below.
const locations = /*[[${locations}]]*/ null;
locations.forEach(location => {
location.description = 44;
location.lnglat = 127;
location.fun(); // error
});
description and lnglat are variables from Java list of objects of class 'Location'; fun is a function from this class.
There is no problem assigning values to variables, but I can't call the function fun. Unfortunately, I don't know what the reason is and how can I fix it.
In Java, I use Spring Boot and method addAttribute:
#GetMapping("/")
public String homePage(Model model) {
model.addAttribute("locations", locations());
return "home";
}
Hi team im have a very small question, i have this code:
constructor(private mapper: any, private applyCallback: (arg0: INode) => void) {
super()
/** #type {GanttMapper} */
this.mapper = new GanttMapper(datamodel);
/** #type {function(INode):void} */
this.applyCallback = applyCallback;
// create the dummy node
this.dummyNode = new SimpleNode();
// switch off the default template
//type RouterClass = typeof ;
//interface RouterDerived extends RouterClass { }
console.log("routerClass");
this.template = new IVisualTemplate({
createVisual() {
return null;
},
updateVisual() {
return null;
}
});}
but in the part:
this.template = new IVisualTemplate({
createVisual() {
return null;
},
updateVisual() {
return null;
}
});
im have an error and the error is this: Cannot create an instance of an abstrac class
in JS this code is working fine but im trying to migrate it in angular and is not working.
im read the other part of same issues but i cant solved it......
im try all tipes of replays but is not working.
Thanks all.
This is because Angular (or rather TypeScript) is more strict than JavaScript. There are things you can do in JavaScript that cannot properly be expressed in a TypeScript definition file, without weakening the type-checks.
This is one of those things. It will work at runtime, but the TypeScript compiler does not understand this feature and will warn you not to use that unrecognized construct.
It's a feature, unique to the yFiles class library and it's called quick interface implementation.
The documentation also states this:
Quick Interface Implementation in TypeScript
The TypeScript .d.ts file doesn’t contain typings that allow quick interface implementations. They still work at runtime, but the TypeScript compiler will produce an error nonetheless. To work around this issue, you can either tell the compiler to ignore the offending line, or use an anonymous implementation of the interface:
// #ts-ignore
const customHitTestable = new IHitTestable((context, location) => location.x > 25)
const customVisualCreator = new class extends BaseClass<IVisualCreator>(IVisualCreator) {
createVisual(context: IRenderContext): Visual {
return new SvgVisual(document.createElementNS("http://www.w3.org/2000/svg", "g"))
}
updateVisual(context: IRenderContext, oldVisual: Visual): Visual {
return oldVisual
}
}
In newer version of yFiles for HTML (since 2.3), there is now a convenient short-hand that works great with typescript:
const customHitTestable = IHitTestable.create((context, location) => location.x > 25)
This does not require the ts-ignore and works for all interfaces: If there is only one method in the interface, all you need to do is pass its implementation to the .create method on the interface object. If there are more members, they need to be passed in via an option object with the names of the members. The typescript definition file provides full completion for this usecase, now.
{
createVisual() {
return null;
},
updateVisual() {
return null;
}
}
This is not valid JavaScript, try
{
createVisual: () => {
return null;
},
updateVisual: () => {
return null;
}
}
here is the code of IVisualTempleate
I imagine it would look like the following.
Server(C#):
public class MyHub : Hub {
...
public int DoSomething(Func<int> fn) {
var res = fn();
return res;
}
...
}
Client(TS/JS):
myHub.invoke('DoSomething', () => 2 + 2).then(res => console.log(res));
However, with this code fn is null on the server.
Seems this is impossible as your parameters should be serializable. So all you can - serialize parameters in known structure and generate invocation function based on deserialization result.
You could write the function in c# and pass it back to the server as a string, then compile and run it - this might help with compiling:
http://www.codeproject.com/Tips/715891/Compiling-Csharp-Code-at-Runtime
I have a C++ method (which role is killing some processes). She needs 2 parameters : a hostname and a port.
On the other hand, I am developing a web-application (using Nodejs and AngularJS), running on Google Chrome.
When I click on a button through the browser, I would like to be able to call the C++ function, through my app.js file.
I haven't found how to "bind" javascript with C++.
EDIT : I think this link could be very useful
How can I use a C++ library from node.js?
You can use Google's V8. V8 is Google's open source JavaScript engine.
V8 can run standalone, or can be embedded into any C++ application.
http://code.google.com/p/v8/
Following example from github demonstrates, binding a C++ class with Google V8.
v8_wrap_class.cpp - Author is nicholas
/*
* v8_wrap_class.cpp
*
* Created on: 14/01/2013
* Author: nicholas
* License: public domain
*/
#include <v8.h>
#include <cstdio>
#include <string>
#include <stdexcept>
#include <memory>
using namespace v8;
/*
var Simple = function(v)
{
this.value = v;
}
Simple.prototype.func = function()
{
alert(this.value);
}
var obj = new Simple(4);
obj.func();
*/
struct Simple
{
double value;
Simple(double v)
: value(v)
{
fprintf(stderr, "Simple::ctor\n");
}
void func()
{
fprintf(stderr, "Simple::func(%f)\n", value);
}
~Simple()
{
fprintf(stderr, "Simple::dtor\n");
}
};
namespace js
{
/*
* Retrieve the c++ object pointer from the js object
*/
template <typename T>
T* unwrap(const Arguments& args)
{
auto self = args.Holder();
auto wrap = Local<External>::Cast(self->GetInternalField(0));
return static_cast<T*>(wrap->Value());
}
/*
* Construct a new c++ object and wrap it in a js object
*/
template <typename T, typename... Args>
Persistent<Object> make_object(Handle<Object> object, Args&&... args)
{
auto x = new T(std::forward<Args>(args)...);
auto obj = Persistent<Object>::New(object);
obj->SetInternalField(0, External::New(x));
obj.MakeWeak(x, [](Persistent<Value> obj, void* data)
{
auto x = static_cast<T*>(data);
delete x;
obj.Dispose();
obj.Clear();
});
return obj;
}
}
void bind_Simple(Local<Object> global)
{
// Name the class in js
auto name = String::NewSymbol("Simple");
auto tpl = FunctionTemplate::New([&](const Arguments& args) -> Handle<Value>
{
if (!args.IsConstructCall())
return ThrowException(String::New("Cannot call constructor as function"));
HandleScope scope;
// Read and pass constructor arguments
js::make_object<Simple>(args.This(), args[0]->NumberValue());
return args.This();
});
tpl->SetClassName(name);
tpl->InstanceTemplate()->SetInternalFieldCount(1);
auto prototype = tpl->PrototypeTemplate();
// Add object properties to the prototype
// Methods, Properties, etc.
prototype->Set(String::New("func"), FunctionTemplate::New([](const Arguments& args) -> Handle<Value>
{
auto s = js::unwrap<Simple>(args);
s->func();
return {};
})->GetFunction());
auto constructor = Persistent<Function>::New(tpl->GetFunction());
global->Set(name, constructor);
}
int main()
{
std::string js_source = R"(
for(var i = 0; i < 1000; ++i)
{
var s = new Simple(4);
s.value = 5;
s.func();
}
)";
/*
* This code is mostly uninteresting.
* Just run the vm with the script provided.
*/
{
HandleScope handle_scope;
Handle<ObjectTemplate> global_template = ObjectTemplate::New();
Persistent<Context> context = Context::New(0, global_template);
Context::Scope context_scope(context);
auto global = context->Global();
// Wrap the class and bind to the global scope.
bind_Simple(global);
{
HandleScope handle_scope;
TryCatch trycatch;
Local<String> source = String::New(js_source.c_str(), js_source.size());
Local<Script> script = Script::Compile(source);
if (script.IsEmpty())
{
Handle<Value> exception = trycatch.Exception();
String::AsciiValue exception_str(exception);
throw std::runtime_error(*exception_str);
}
Local<Value> result = script->Run();
if (result.IsEmpty())
{
Local<Value> exception = trycatch.Exception();
String::AsciiValue exception_str(exception);
throw std::runtime_error(*exception_str);
}
}
context.Dispose();
context.Clear();
}
// Run the GC until there is nothing to reclaim.
while (!V8::IdleNotification())
;
return 0;
}
This answer gives four appraoches to using C++ in javascript. The methods shown try to keep the original C++ in a standard and simple C++ implementation.
Two methods using WASM and two using SWIG and JRPC are used to give examples and ideas on executing C++ in javascript. In detail, this answer gives one example for executing in the browser and one for executing in nodejs using WASM. The end of this answer also lists two other ways to execute C++ in javascript, one of which calls nodejs from the browser which is slightly more complex but possible using jrpc-oo.
If you want to execute in the browser or nodejs, then you can compile your C++ to WASM and load that module in the browser or nodejs, executing the C++ there. This WASM repo exemplifies how to do that. I will expand the key code in the WASM repo here.
Create some C++ code and declare your WASM binding, for example (from the files include/Test.H and src/Test.C) :
class Test {
public:
void sayHello(){
printf("Hi, my name is test\n");
}
};
#include <emscripten/bind.h>
EMSCRIPTEN_BINDINGS(Test_ex) {
emscripten::class_<Test>("Test")
.function("sayHello", &Test::sayHello)
;
}
Compile that down and you can now run that in nodejs (from the file nodejs/WASMTestNode.js) :
libWASM = require('./libwasmNode.js');
libWASM().then((mod)=>{
libWASM = mod;
let test = new libWASM.Test;
test.sayHello();
});
In the browser you can use the WASM code as well. To do that firs import the WASM library (from the file webcomponent/libWASM.js) :
import modProm from './libwasm.js';
The create your webcomponent and compile your WASM then execute the C++ method Test::sayHello (from the file webcomponent/libWASM.js) :
import { LitElement } from 'lit';
export class LibWASM extends LitElement {
constructor() {
super();
modProm().then((mod)=>{
this.libWASM = mod; // for rendered wasm that delay
this.WASMReady();
})
}
WASMReady(){
console.log('LibWASM.libWASM module compiled and ready to go.')
let test = new this.libWASM.Test;
test.sayHello();
}
}
This code example is implemented in the reference repo.
Alternatively a third way to do this is to only use C++, SWIG and nodejs from this reference repo.
If you want to execute nodejs from the browser or use a a different method of integrating C++ into nodejs, you can also look at this SWIG and jrpc-oo reference for doing the same thing, but not only in nodejs, also from the browser calling nodejs.
There are also other ways to execute C++ form javascript, however the methods demonstrated in this answer are reasonably straightforward and either rely on WASM binding or SWIG abstraction which leaves your original code as standard C++.