[Node.js] Edge.js を触ってみた


[[C#] Edge.js を触ってみた](http://qiita.com/t-koyama/items/c6070c89bb5bd6f15fa7) で C# 側から Node.js を使うのをメモしましたので、今回は javascript 側から C# を使うところを少しメモしておこうと思います。

Edge.js についての詳細は以下を参照してください。

* [Edge.js](http://tjanczuk.github.io/edge/#/)
* [GitHub - tjanczuk/edge: Run .NET and Node.js code in-process on Windows, MacOS, and Linux](https://github.com/tjanczuk/edge/tree/master)

# 開発環境
* Windows 10 Pro
* Visual Studio Code
* Node.js v0.12.2

# 準備
npm を使って Edge.js をインストールします。

```bash
npm install edge
```

# Hello World
Hello World してみます。

```javascript:sample1.js
var edge = require('edge');

var sample1 = edge.func(function () {/*
    async (input) =>
        "Hello " + input.ToString() + "!";
*/});

sample1('World', function (error, result) {
    if (error) throw error;
    console.log(result);
});
```

`edge.func()` を使って C# のスクリプトを文字列として引数に渡してあげると、 javascript で使えるようになります。また、引数で与える C# のラムダには `async` をつける必要があります。 

# 匿名型を受け取る
匿名型を受け取ってみます。

```javascript:sample2.js
var edge = require('edge');

var sample2 = edge.func(function () {/*
    using System.Collections.Generic;
    using System.Linq;

    async (input) => { 
        var items = ((object[])input).Cast<int>();
        var filter = items.Where(x => x % 2 == 0);
        var map = items.Select(x => x * 2);
        var sum = items.Sum();

        Console.WriteLine("-- csharp ---------------------------------------------------");
        Console.WriteLine(string.Join(",", filter));
        Console.WriteLine(string.Join(",", map));
        Console.WriteLine(sum);

        return new
        {
            filter = filter,
            map = map,
            sum = sum
        };
    };
*/});

sample2([1, 2, 3, 4, 5], function (error, result) {
    if (error) throw error;
    console.log('-- javascript -----------------------------------------------')
    console.log(result);
});
```

javascript 側から 配列を引数で渡して、C# 側でごにょごにょしてから匿名型にして返してます。
上の例では System.Collections.Generic 名前空間と System.Linq 名前区間を使っているので先頭で `using` しています。

# クラスを定義して使う場合
クラスを定義する必要がある場合は以下のように記述します。

```csharp
using System.Threading.Tasks;

public class Startup
{
    public async Task<object> Invoke(object input)
    {
        ...
    }
}

class Sample
{
    ...
}
```

`Startup` クラスの `Invoke()` メソッドが javascript で `edge.func()` したときに呼ばれる部分となります。

また、ラムダで記述する場合は `System` と `System.Threading.Tasks` はデフォルトで `using` されているようですが、クラスを定義して使用する場合は上の例のように自分で `using` する必要があります。

# クラスを受取る
クラスを定義して受取ってみます。
 
```javascript:sample3
var edge = require('edge');

var sample3 = edge.func(function () {/*
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    public class Startup
    {
        public async Task<object> Invoke(object input)
        {
            var items = ((object[])input).OfType<int>();
            var filter = items.Where(x => x % 2 == 0);
            var map = items.Select(x => x * 2);
            var sum = items.Sum();

            Console.WriteLine("-- csharp ---------------------------------------------------");
            Console.WriteLine(string.Join(",", filter));
            Console.WriteLine(string.Join(",", map));
            Console.WriteLine(sum);

            return new Sample
            {
                Filter = filter,
                Map = map,
                Sum = sum
            };
        }
    }
    
    public class Sample
    {
        public IEnumerable<int> Filter { get; set; }
        public IEnumerable<int> Map { get; set; }
        public int Sum { get; set; }
    }
*/});

sample3([1, 2, 3, 4, 5], function (error, result) {
    if (error) throw error;
    console.log('-- javascript -----------------------------------------------')
    console.log(result);
});
```

`Sample` クラスを作成して返してます。それ以外は匿名型の時と同じです。

# cs/csx ファイルの読込
C# のコードは別ファイルにすることもできます。javascript 側で呼び出すときは以下のようにします。

```javascript
var sample4 = edge.func(require('path').join(__dirname, 'sample4.cs'));
```

# Win32 API
呼べるらしいので試します。

### C#  

```csharp:beep.cs
using System.Runtime.InteropServices;
using System.Threading.Tasks;

public class Startup
{
    [DllImport("kernel32.dll")]
    private extern static bool Beep(uint dwFreq, uint dwDuration);

    public async Task<object> Invoke(dynamic input)
    {
        Beep((uint)input.freq, (uint)input.duration);
        return null;
    }
}
```

### javascript

```javascript:sample5.js
var edge = require('edge');

var beep = edge.func(require('path').join(__dirname, 'beep.cs'));

beep({ freq : 262, duration : 500}, function (error, result) {
    if (error) throw error;
});
```

kernel32.dll にあるBeep関数を呼び出して音を鳴らしています。Beep関数は音の高さと長さを指定することができるので javascript 側で高さと長さを指定してあげるために連想配列にして渡しています。

javascript 側から引数で連想配列を渡すときなど、 `Invoke()` メソッドの引数の型を `object` 型 の代わりに `dynamic` 型にすることも可能です。

# XML
XMLファイルの内容をC# 側でオブジェクトにデシリアライズしてみます。

### XMLファイルの内容

```xml:sample.xml
<?xml version="1.0" encoding="utf-8"?>
<members>
  <person id="1">
    <name>太郎</name>
    <age>16</age>
  </person>
  <person id="2">
    <name>次郎</name>
    <age>35</age>
  </person>
  <person id="3">
    <name>三郎</name>
    <age>21</age>
  </person>
  <person id="4">
    <name>ケビン</name>
    <age>42</age>
  </person>
</members>
```

`members` 要素をルートとして `person` 要素がずらずらと並ぶ感じです。

### C#  

```csharp:sample.cs
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Xml.Serialization;

public class Startup
{
    public async Task<object> Invoke(string path)
    {
        // ファイルを開く
        using (var stream = new FileStream(path, FileMode.Open))
        {
            // デシリアライズする
            var serializer = new XmlSerializer(typeof(Members));
            var model = (Members)serializer.Deserialize(stream);
            
            // person 要素を返す
            return model.Person;
        }
    }

    [XmlRoot("members")]
    public class Members
    {
        [XmlElement("person")]
        public List<Person> Person { get; set; }
    }

    public class Person
    {
        [XmlAttribute("id")]
        public int Id { get; set; }

        [XmlElement("name")]
        public string Name { get; set; }

        [XmlElement("age")]
        public int Age { get; set; }
    }
}
```

XMLドキュメントの内容に合うようにモデルを用意してデシリアライズしています。
デシリアライズした結果から `person` 要素にあたる部分のコレクションを返しています。

### javascript

```javascript:sample6.js
var edge = require('edge');

var xml = edge.func({
    source : require('path').join(__dirname, 'sample.cs'),
    references : ["System.Xml.dll"]
});

xml(require('path').join(__dirname, 'sample.xml'), function (error, result) {
    if (error) throw error;
    result.forEach(function(element) {
        console.log("id:" + element.Id + ", name: " + element.Name + ", age: " + element.Age);
    }, this);
});
```

C# 側で System.Xml.Serialization 名前空間を使っているので System.Xml.dll への参照を `references` に追加しています。 

.NET を使うと jQuery とは一味違った感じでXML操作ができそうです。

# 他にも
公式のサイトでは [System.Drawing を使って画像ファイルの形式を変えて保存](https://github.com/tjanczuk/edge/blob/master/samples/208_convertImage.js)したり、[Zipファイルを生成](https://github.com/tjanczuk/edge/blob/master/samples/207_zip.js)したり、 [ADO.NET を使ってデータベースに接続](https://github.com/tjanczuk/edge#how-to-script-t-sql-in-a-nodejs-application)したりする方法などいろいろ紹介されています。

# まとめ
Edge.js を利用すれば Node.js で .NET のライブラリや Windows であれば Win32 API が使えます。また、今回は C# でやりましたが、 F# や Python (IronPython) など .NET 上で動く言語であれば利用できます。

簡単なメモ書きですが Edge.js に興味を持っていただけたら幸いです。

# 参考
_Edge.js_
[http://tjanczuk.github.io/edge/#/](http://tjanczuk.github.io/edge/#/)

_GitHub - tjanczuk/edge: Run .NET and Node.js code in-process on Windows, MacOS, and Linux_
[https://github.com/tjanczuk/edge/tree/master](https://github.com/tjanczuk/edge/tree/master)