Skip to content

This project allows using all .NET Core functionality on Windows, GNU/Linux and Mac from Node.js

Notifications You must be signed in to change notification settings

kodhework/netrpa.js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NetRPA.js (NET Core inside Node.js)

This project allows using all .NET Core functionality on Windows, GNU/Linux and Mac from Node.js. This project aims to be a replacement of edge module, without relying on native (node-pregyp) dependencies

By default you can compile dynamically C# source code

Requirements

  1. @kawix/core or Node 10+
  2. .NET Core 3.0.0 runtime or superior
  3. GNU/Linux, Windows or Mac (no tested on Mac should works, please make an issue if not)
  4. .NET Core 3.0.0 SDK or superior if you want build the C# project

Using from github repo

Clone and build c# project

  1. Install @kawix/core
  2. Clone this project and execute build file
git clone https://github.com/kodhework/netrpa.js
cd netrpa.js 
kwcore build

Dll files are copied to bin/netcore

  1. Now you can use in your project

Using @kawix/core:

import {Channel as NetRPA} from '/path/to/netrpa/Channel'

main()
async function main(){
    let channel = await NetRPA.create()
    // ....
}

Or using node.js

Install the modules:

npm install @kawix/core
require("@kawix/core")
var NetRPAMod = require("/path/to/netrpa")

NetRPAMod.ready(function(NetRPA){
    NetRPA.create().then(function(channel){
        // ....
    }).catch(function(){})
}).error(function(e){
    console.error(e)
})

NPM module

(coming soon)

Usage

  • See how NetRPA can be compatible with edge module for lambdas:
main()
async function main(){
    
    var Channel = await NetRPA.create()
    var CsharpCompiler = await Channel.client.CSharpCompiler()
    var helloWorld = await CsharpCompiler.CompileLambdaString(`
    async (input) => {
        return ".NET Welcomes " + input.ToString();
    }
    `)
    console.info(await helloWorld("Javascript"))

}

Previous example will work, but you can get faster results using full Class declaration with strong types:

main()
async function main(){
    
    var Channel = await NetRPA.createChannel()
    var CsharpCompiler = await Channel.client.CSharpCompiler()
    var TestClass = await CsharpCompiler.CompileString(`
    class Test{
        public string Hello(string input){
            return ".NET Welcomes " + input;
        }
    }
    `, 'Test')
    console.info(await TestClass.Hello("Javascript"))

}

This second example produces the same results, but will be quite faster than compiling lambda.

Why NetRPA?

  • No native dependencies
  • Allow using any class in NET Core environments (maybe later NET framework)
  • Easier for use than edge.js and Faster in some cases
  • Always async javascript client side
  • No require follow an strict declaration style

How works NetRPA?

NetRPA use RPA protocol, is a custom made and good design protocol for inter process comunication. In Unix uses unix sockets, in windows use named pipes. You can create a RPA Channel to .NET Core using:

var Channel = await NetRPA.createChannel()

Channel have some methods like:

class NetRPA.Channel{
    plain(target: object){
        // gets and object that can be used as parameters for Call,
        // indicates arguments will be serialized, and not proxied to NET core
    }

    get client() : NetRPA.AssemblyManager {
        // get the main NET Core  service(object)  exported  
        // The service is a NetRPA.AssemblyManager instance Proxy
        // The NetRPA.AssemblyManager is defined on c# code
    }
}

You can inspect the code of AssemblyManager in the c# project. Here some of its usefull methods:

interface TypeDef{
    type: string | TypeDef,
    generic?: Array<string | TypeDef>
}

class NetRPA.AssemblyManager{
    async LoadAssemblyFile(file: string) : Promise<void>{
        // Load an assembly file. Types present on assembly will be loaded
        // and available for use in GetType and GetTypeDefinition methods
        // NOTE: Assembly loaded with this method, will be referenced in future compilations
        await Channel.client.LoadAssemblyFile("/path/to/mydll")
    }

    async LoadAssembly(name: string) : Promise<void>{
        // Load an assembly by full name. Types present on assembly will be loaded
        // and available for use in GetType and GetTypeDefinition methods
        // NOTE: Assembly loaded with this method, will be referenced in future compilations   
    }

    async LoadAssemblyPartialName(name: string) : Promise<void>{
        // Load an assembly by partial name. This is deprecated in .NET. Types present on assembly will be loaded
        // and available for use in GetType and GetTypeDefinition methods
        // NOTE: Assembly loaded with this method, will be referenced in future compilations   
        await Channel.client.LoadAssemblyPartialName("System.Xml.dll")
    }

    async LoadAssembly(name: System.Reflection.Assembly): Promise<void>{
        // Load an assembly and types present on assembly will be loaded
        // and available for use in GetType and GetTypeDefinition methods
        // NOTE: Assembly loaded with this method, will be referenced in future compilations   

        // example: 
        var assembly = await (await Channel.client.GetType("System.String")).get_Assembly()
        // of course this is not required, because mscorlib and System.dll is loaded by default
        await Channel.client.LoadAssembly(assembly)

    }




    async GetType(type: string | TypeDef) : Promise<System.Type>{
        // returns the System.Type from type parameter
        // The Net core type should be loaded using LoadAssembly methods in this object

        // example 
        var Type = await Channel.client.GetType({
            type: 'System.Collections.Hashtable'
        })
        console.info(await Type.get_AssemblyQualifiedName())

    }

    async GetTypeDefinition(type: string | TypeDef) : Promise<Proxy>{
        // return a proxied instance representing the Static Class of the NET Core Type
        // The Net core type should be loaded using LoadAssembly methods in this object
        // How use Generic Types? 
        //
        
        // this an example: 
        var collectionClass = await Channel.client.GetTypeDefinition({
            type: 'System.Collections.Generic`2'
            generic: ['System.String', 'System.Object']
        })
        var collection = await collectionClass[".ctor"]()
        await collection.Add("name", "James")
        await collection.Add("age", 25)

    }


    async CreateNewScope() : Promise<NetRPA.AssemblyManager>{
        // create a new instance of assemblymanager
        // is usefull if you want compile code with different referenced assemblies

        await Channel.client.LoadAssemblyPartialName("System.Xml.dll")
        // OK 
        var XmlDocumentClass = await Channel.client.Construct("System.Xml.XmlDocument")
        await XmlDocumentClass.Load("/path/to/file.xml")
        
        var scope = await Channel.client.CreateNewScope()
        // throws error, System.Xml dll not loaded
        XmlDocumentClass = await scope.Construct("System.Xml.XmlDocument")

    }

    async CSharpCompiler(): Promise<NetRPA.Compiler.CSharp>{
        // return an instance of compiler for csharp code (Roslyn)
    }

    async Construct(type: TypeDef | string, params?: Array<object>): Promise<Proxy>{
        // construct an instance of .NET Core type
        // example: 


        var collection = await Channel.client.Construct({
            type: 'System.Collections.Generic`2'
            generic: ['System.String', 'System.Object']
        })

        // is the same:         

        var collectionClass = await Channel.client.GetTypeDefinition({
            type: 'System.Collections.Generic`2'
            generic: ['System.String', 'System.Object']
        })
        var collection = await collectionClass[".ctor"]()
    }

}


class NetRPA.Compiler.CSharp{
    async CompileLambdaString(source: string):
        Promise<System.Func<object, System.Threading.Tasks.Task<Object>>>
    {
        // compile a lambda function and returns as
        // System.Func<object, System.Threading.Tasks.Task<Object>> proxied object
        // (callable as a function in javascript or using Invoke method)

        var CsharpCompiler = await Channel.client.CSharpCompiler()
        var helloWorld = await CsharpCompiler.CompileLambdaString(`
        async (input) => {
            return ".NET Welcomes " + input.ToString();
        }
        `)

        // using as function
        console.info(await helloWorld("Javascript"))

        // using Invoke method
        console.info(await helloWorld.Invoke("Javascript"))
    }

    async CompileString(source: string, type?: string): Promise<Proxy>{

        // compile a string as an assembly, and optionally returns 
        // a proxy object representing the static class  passed as parameter type

        var CsharpCompiler = await Channel.client.CSharpCompiler()
        var TestClass = await CsharpCompiler.CompileString(`
        class Test{
            public string Hello(string input){
                return ".NET Welcomes " + input;
            }
        }
        `, 'Test')
        console.info(await TestClass.Hello("Javascript"))

        // you can also omit the type parameter, and use like any other class

        CsharpCompiler = await Channel.client.CSharpCompiler()
        await CsharpCompiler.CompileString(`
        namespace MyNamespace{
            class Important{
                public string Hello(string input){
                    return ".NET Welcomes " + input;
                }
            }
        }
        `)
        var ImportantClass = await Channel.client.Construct("MyNamespace.Important")
        console.info(await ImportantClass.Hello("Javascript"))

    }




}

RPA Serialization

RPA Provides two types of serialization from javascript to C#:

  • Plain objects passed in c# as DynamicRemoteObject (IDictionary<string, object>). Arrays are passed as object[]
main()
async function main(){
    
    var Channel = await NetRPA.createChannel()
    var CsharpCompiler = await Channel.client.CSharpCompiler()
    var TestClass = await CsharpCompiler.CompileString(`
    class Test{
        public void ReadInput(dynamic input){
            Console.WriteLine("Your name is " + (string)input.name + " and your age is " + input.age.ToString() );
        }
    }
    `, 'Test')
    await TestClass.Hello(Channel.plain({
        name: 'James',
        age: 25
    }))

}
  • Proxied objects passed in c# as DynamicRemoteObject (DynamicObject, IDictionary<string, object>), and if is array passed as: DynamicRemoteArrayObject (DynamicObject, IDictionary<string, object>, IList)

    NOTE: By default, RPA uses a mixed Plain/Proxied serialization. If parameter protype is object, all properties with primitive values will be passed as plain, but also is passed as Proxy for make available its functions. For understand this see the example:

    main()
    async function main(){
        
        var Channel = await NetRPA.createChannel()
        var CsharpCompiler = await Channel.client.CSharpCompiler()
    
        
    
        var TestClass = await CsharpCompiler.CompileString(`
        class Test{
            public async Task<int> ReadInput(dynamic input){
                
                return await input.add(input.a, input.b);
            }
        }
        `, 'Test')
    
        // in c# code 
        // input.a and input.b can be readed sync because will be passed
        // plain serialized, and function input.add can be called only async, because is proxied
        await TestClass.ReadInput({
            a: 10,
            b: 5,
            add(a, b){
                return a + b
            }
        })
    }

    From C# to Javascript

    • All types primitive (string,int,float,etc) will be serialized in plain mode.

    • All object types will be Proxied to javascript (not mixed like javascript to C#)

    main()
    async function main(){
        
        var Channel = await NetRPA.createChannel()
        var CsharpCompiler = await Channel.client.CSharpCompiler()
    
        
    
        var TestClass = await CsharpCompiler.CompileString(`
        class Person{
            public string Name{get;set;}
            public int Age{get;set;}
        }
        class Test{
            public Person CreatePerson(){
                var person= new Person();
                person.Name = "James";
                person.Age = 25;
                return person; 
            }
        }
        `, 'Test')
    
        
        var person = await TestClass.CreatePerson()
        // from javascript can access to properties only by async calls 
        // because object is proxied 
        console.info("My name: ", await person.get_Name(), " my age: ", await person.get_Age())
    }
    • You can also pass types plain serialized, using the type NetRPA.Value
    main()
    async function main(){
        
        var Channel = await NetRPA.createChannel()
        var CsharpCompiler = await Channel.client.CSharpCompiler()
    
        
    
        var TestClass = await CsharpCompiler.CompileString(`
        class Person{
            public string Name{get;set;}
            public int Age{get;set;}
        }
        class Test{
            public Value<Person> CreatePerson(){
                var person= new Person();
                person.Name = "James";
                person.Age = 25;
                return new Value<Person>(person); 
            }
        }
        `, 'Test')
    
        
        var person = await TestClass.CreatePerson()
        // from javascript can access to properties in sync mode
        // because object is plain serialized 
        console.info("My name: ", person.Name, " my age: ", person.Age)
    }

    Exceptions

    Exceptions are fully managed with NetRPA.

    Remember always use the await keyword when calling functions from CLR to Node.js or viceversa.

    // BAD, you get an unresolved promise
    // and your app will close due to UncaughtException 
    var type = channel.client.Construct("System.NonExistentType")
    
    // GOOD you can get the error message
    try{
        var type = await channel.client.Construct("System.NonExistentType")
    }catch(e){
        console.error(e)
    }

    Migrate from edge / Samples

    You can see the samples folder. Current samples are extracted from npm edge package, and translated to use with NetRPA. You can run the samples easy:

    cd samples
    kwcore 207_zip

    Created by

    Kodhe, Copyright 2019©

    You can contribute reporting bugs, improving README, or adding functionality. You are welcome to contribute

About

This project allows using all .NET Core functionality on Windows, GNU/Linux and Mac from Node.js

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published