Scratch から Arduino を操作しよう、というわけで NetScrattino を作る

Scratch を使って Arduino を動かそうとすると http://scratchx.org/ を使うのがいいのだろうけど、どうも自分の環境ではうまく動かない。オフラインの Scratch 2.0 の場合、ファイルメニューをシフトを押しながら開くと「実験的なHTTP拡張を取り込み」というのが出て、適当なHTTPサーバーを作ると繋がるらしいことが分かった。

サーバーを作るのが手間といえば手間なんだけど(ScratchXの場合は、Chrome拡張をインストールすると、Chrome側にHTTPサーバーを立てる仕組みになっている)、一度作っておけば、Arduino 以外に接続するのも楽ではないかなと思って、作ってみることにする。.NET で作れば HttpListener があるので、何とかなるのではないかな、と。

Scrattino 2 | Yengawa Systems
http://www.yengawa.com/scrattino2
Let’s Make With Arduino!
https://lets.makewitharduino.com/sample/scratch/

Scrattino2 のほうは、ArduinoにFirmataを入れるんだけど、HTTPサーバーはMac版しかない。Scratio のほうは、独自プロトコルにしてあって中身は Python で書かれている。
ざっと、Scratio で Scratch の拡張ブロックの操作を確認したところで、まあいけそうなことが分かったので Firmata への接続を作ることにした。

シリアル通信で Firmata に接続する

まずは、NetScrattino から Arduino にシリアル通信する。

シリアル通信は双方向に通信ができるので、NetScrattinoからコマンドを送信すると同時に、定期的に Arduino のほうからアナログピンの状態を送信してくれる。これを保持しておく。

protocol/protocol.md at master ・ firmata/protocol
https://github.com/firmata/protocol/blob/master/protocol.md
firmataプロトコル覚え書き
https://gist.github.com/hiroeorz/7868628

あたりを見ながら、ひとまずデジタルピンとアナログピンの読み書き、モードの設定、レポートの設定だけを送れるようにしておく。

// two byte digital data format, second nibble of byte 0 gives the port number (e.g. 0x92 is the third port, port 2)
// 0  digital data, 0x90-0x9F, (MIDI NoteOn, but different data format)
// 1  digital pins 0-6 bitmask
// 2  digital pin 7 bitmask 
member this.digitalWrite(pin,value) =
    let portNumber = (pin >>> 3) &&& 0xFF
    digitalInputData.[portNumber] <-
        if value = 0 then
            digitalInputData.[portNumber] &&& ~~~(1 <<< (pin &&& 0x07))
        else
            digitalInputData.[portNumber] ||| (1 <<< (pin &&& 0x07)) 
    let message = [|
        DIGITAL_MESSAGE ||| byte(portNumber) 
        byte(digitalInputData.[portNumber] &&& 0x7F)
        byte(digitalInputData.[portNumber] >>> 7)
    |]
    _socket.Write(message, 0, message.Length);

あれこれ面倒なので、F# で書いたのであった。
Arduino から非同期で送ってくるデータは、DataReceived で受け取る。

_socket.DataReceived.Add( fun (e) -> 
    while _socket.BytesToRead > 0 do
        let head = _socket.ReadByte() |> byte
        match head with
        // analog 14-bit data format
        // 0  analog pin, 0xE0-0xEF, (MIDI Pitch Wheel)
        // 1  analog least significant 7 bits
        // 2  analog most significant 7 bits
        | h when ANALOG_MESSAGE <= h && h <= ANALOG_MESSAGE + 15uy -> 
            let pin = int(h - ANALOG_MESSAGE)
            let lsb = _socket.ReadByte()
            let msb = _socket.ReadByte()
            let data = (msb <<< 7) ||| lsb
            analogInputData.[pin] <- data
        // two byte digital data format, second nibble of byte 0 gives the port number (e.g. 0x92 is the third port, port 2)
        // 0  digital data, 0x90-0x9F, (MIDI NoteOn, but different data format)
        // 1  digital pins 0-6 bitmask
        // 2  digital pin 7 bitmask 
        | h when DIGITAL_MESSAGE <= h && h <= DIGITAL_MESSAGE + 15uy -> 
            let pin = int(h - DIGITAL_MESSAGE)
            let lsb = _socket.ReadByte()
            let msb = _socket.ReadByte()
            let data = (msb <<< 7) ||| lsb
            digitalInputData.[pin] <- data
        | _ -> 
            // read off
            let d = _socket.ReadExisting()
            ()
)

HTTPサーバーを作って Scratch に応答する

Scratch の拡張ブロックは、JSON形式で書くことができて、こんな風になっている。

{
  "extensionName": "Net Scrattino",
  "extensionPort": 5410,
  "url": "https://github.com/yokobond/scrattino2",
  "blockSpecs": [
    [" ", "INPUT %m.digitalPinNames mode %m.inputPinModes", "setMode", "D2", "PULLUP"],
    [" ", "OUTPUT %m.digitalPinNames value %m.digitalValues", "digitalWrite", "D2", 0],
    [" ", "PWM %m.digitalPinNames value %d.pwmValues", "analogWrite", "D2", 0],
    [" ", "SERVO %m.digitalPinNames degree %d.servoValues", "servoWrite", "D2", 0],
    [" ", "Set Pin %m.digitalPinNames to %d.digitalPinModes mode", "setPinMode", "D2", "OUTPUT"],
    [" ", "LED %m.digitalPinNames is %m.OnOffValues", "digitalWrite", "D2", "ON"],
    ["-"],
    ["r", "A0", "a0"],
    ["r", "A1", "a1"],
    ["r", "A2", "a2"],
    ["r", "A3", "a3"],
    ["r", "A4", "a4"],
    ["r", "A5", "a5"],
    ["-"],
//    ["R", "value of %m.digitalPinNames", "pinValue", "D2"],
    ["r", "D2", "d2"],
    ["r", "D3", "d3"],
    ["r", "D4", "d4"],
    ["r", "D5", "d5"],
    ["r", "D6", "d6"],
    ["r", "D7", "d7"],
    ["r", "D8", "d8"],
    ["r", "D9", "d9"],
    ["r", "D10", "d10"],
    ["r", "D11", "d11"],
    ["r", "D12", "d12"],
    ["r", "D13", "d13"]
  ],
  "menus": {
    "digitalPinNames": ["D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10", "D11", "D12", "D13"],
    "analogPinNames": ["A0", "A1", "A2", "A3", "A4", "A5"],
    "digitalPinModes": ["INPUT", "INPUT_PULLUP", "OUTPUT", "PWM", "SERVO"],
    "inputPinModes": ["PULLUP", "PULLDOWN"],
    "digitalValues": [0, 1],
    "OnOffValues": ["ON", "OFF"],
    "pwmValues": [0, 64, 128, 192, 255],
    "servoValues": [0, 45, 90, 135, 180],
    "analogValues": [0, 256, 512, 768, 1023]
  }
}

blockSpecs にあるのがブロックの定義で、これを web api な形で呼び出す。
仕様は、https://wiki.scratch.mit.edu/w/images/ExtensionsDoc.HTTP-9-11.pdf に書かれている。ID を使って非同期にデータを送る方法は面倒なんだが、通常はポーリング(/poll)を送って、データを返すパターンが多いので、それだけならばそんなに難しくはない。
で、これも F# で実装してみる。

let mutable arduino = new FirmataNET.Arduino()

// Scratchから受信するためのHTTPサーバー
let Server( port ) =
    let listener = new System.Net.HttpListener()
    listener.Prefixes.Add(&quot;http://127.0.0.1:&quot;+(port |> string)+&quot;/&quot; )
    listener.Start()
    while true do
        let context = listener.GetContext()
        let res = context.Response
        let mutable data = &quot;&quot;
        let path = context.Request.Url.PathAndQuery
        match path with 
            | &quot;/poll&quot; -> 
                for i=0 to 5 do 
                    data <- data + String.Format(&quot;a{0} {1}\n&quot;, i, arduino.analogRead(i))
                for i=2 to 13 do 
                    data <- data + String.Format(&quot;d{0} {1}\n&quot;, i, arduino.digitalRead(i))
                // デバッグ出力
                let mutable debug = &quot;&quot;
                for i=0 to 5 do 
                    debug <- debug + String.Format(&quot;a{0} {1} &quot;, i, arduino.analogRead(i))
                debug <- debug + &quot;\n&quot;
                for i=2 to 13 do 
                    debug <- debug + String.Format(&quot;d{0} {1} &quot;, i, arduino.digitalRead(i))
                debug <- debug + &quot;\n&quot;
                // printfn &quot;%s&quot; path
                // printfn &quot;%s&quot; debug
            | &quot;/reset_all&quot; ->
                printfn &quot;/reset_all&quot;
                arduino.Reset()
                data <- &quot;ok&quot;
            | _ ->
                let pa = path.Split([|'/'|])
                match pa.[1] with   
                | &quot;digitalWrite&quot; ->
                    let pin = pa.[2].Substring(1) |> int
                    let value = 
                        match pa.[3].ToUpper() with
                        | &quot;ON&quot; -> 1
                        | &quot;OFF&quot; -> 0
                        | _ -> pa.[3] |> int
                    arduino.digitalWrite( pin, value )
                | &quot;analogWrite&quot; ->
                    let pin = pa.[2].Substring(1) |> int
                    let value = pa.[3] |> int
                    arduino.pinMode( pin, 0x03 )    // PWM
                    arduino.analogWrite( pin, value )
                | &quot;servoWrite&quot; ->
                    let pin = pa.[2].Substring(1) |> int
                    let value = pa.[3] |> int
                    arduino.pinMode( pin, 0x04 )    // SERVO
                    arduino.analogWrite( pin, value )
                | &quot;setMode&quot; ->
                    let pin = pa.[2].Substring(1) |> int
                    let value = if pa.[3] = &quot;PULLUP&quot; then 0x0B else 0x00
                    arduino.pinMode( pin, value )
                | &quot;setPinMode&quot; ->
                    let pin = pa.[2].Substring(1) |> int
                    let value = 
                        match pa.[3] with
                        | &quot;INPUT&quot; -> 0
                        | &quot;OUTPUT&quot; -> 1
                        | &quot;PWM&quot; -> 3
                        | &quot;SERVO&quot; -> 4
                        | &quot;INPUT_PULLUP&quot; -> 11
                        | _ -> 1
                    arduino.pinMode( pin, value )
                | _ ->
                    data <- &quot;&quot;
                printfn &quot;%s&quot; path
        res.StatusCode <- 200
        let sw = new System.IO.StreamWriter( res.OutputStream )
        sw.Write( data )
        sw.Close()
    ()

Scratch で拡張ブロックを作ってみる

先に作った JSON を Scratch 2.0 に読み込ませると、自前で作ったブロックが使えるようになる。

旗をクリックしたときとか、スペースキーを押されたとき、などのイベントのブロックがあるが、テストをするときはブロック自体をダブルクリックすれば実行されるので、プロトタイプを作るときには結構便利。mBlock の場合だと、あらかじめ Arduino にデプロイしてしまうので、変更するに書き込まないといけないし。まあ、Firmata 自体がプロトタイプを作るためのものでもあるので、用途的にはちょうどよいかと思う。

簡易プロキシにUIを付ける

最初は、コマンドラインだけでやっていたのだが、Firmata を直接扱えたほうが便利なので、簡易プロキシ(NetScrattino)にUIを付けてみる。

これは WPF で作って、内部的に MVVM パターンになっているので、この解説はまた後で。

Scratchと連携させる

せっかくの Scratch なので、Arduino を操作するだけじゃなくて猫のほうも操作できるようにしておく。

これは、Lチカをしながら猫が走るパターン。LEDをマウスでクリックすると、Arduino上のLEDが光ると同時に絵のLEDも光る。

ざっと、簡単なものとして、

  • LEDの点滅
  • PWMでLEDの点灯
  • サーボを動かす
  • ポテンショメーター(回転とかスライダーとか)でアナログピンで読み取る

なところまでできた。後は、順次

スクラッチーノでScratchとArduinoをつなぐ – MeiDe Digital Craft 2016
https://sites.google.com/site/meidedigitalcraft2016/knowhow/scrattino-usage

にある実習を動かすようなプログラムが組めればよいかな。

コード

NetScrattino のコードはこちら

moonmile/NetScrattino: Simple Server to connect from Scratch to Arduino
https://github.com/moonmile/NetScrattino

これから

ScratchX
http://scratchx.org/#extensions

の拡張を見ていくと Kinect とか Leapmotion とかもある。環境が悪いのかよくわからにけど、うちの PC では ScratchX が動かないので何とも言えないのだけど、どうやら、COM 制限のような気がする。このあたりは、別の PC や Mac で試してみよう。

ローカルで実験する場合は、HTTP プロキシを作ったほうが応用範囲が広そうなので(.NETで作れるし)、カメラでの撮影を Scratch 側で制御するとか、物体認識を Scratch に持って来るというのもできそうな感じはする。

カテゴリー: 開発, F#, Scratch | Scratch から Arduino を操作しよう、というわけで NetScrattino を作る はコメントを受け付けていません

ちょっと雑だが、C# で JsonProvider もどきを作る

F# には TypeProvider というのがあって、動的にクラスを作り、それを F# のコードで扱えるものです。

F# Data: JSON 型プロバイダー
https://fsharp.github.io/FSharp.Data/ja/library/JsonProvider.html

でもって、C# には TypeProvider がないんので F# が羨ましかったり、いやそう言うなら F# で組めばいい訳ですが。以前、F# の XAML の TypeProvider を作ったときに、そのまま Xamarin の PCL には持って行けなくて諦めた覚えがあるんですが。今だともうちょっと工夫できるかも、ってことで、C# で TypeBuilder を使ってみます。

雑に JsonProvider を作る

JSON の文字列を渡して、それをプロパティに持つクラスを作ります。実は、Newtonsoft.Json は dynamic を持っているので、あまり意味はない…というか、結論から言えば T4 とか CodeDOM を使ったほうが早いのでは?という感じはします。

class Program
{
    static void Main(string[] args)
    {
        var json = @"{ name: 'tomoaki', num: 101 }";
        var jp = new JsonProvider(json);
        var t = jp.Make("SampleJson");

        var o = new SampleJson();
        o.name = "aaaa";
        o.num = "100";
    }
}
public class JsonProvider
{
    JObject root;
    Dictionary<string, string> dic = new Dictionary<string, string>();

    public JsonProvider( string json )
    {
        root = JObject.Parse(json);
        var cur = root.GetEnumerator();
        while ( cur.MoveNext() )
        {
            var it = cur.Current;
            Debug.WriteLine("{0} {1}", it.Key, it.Value);
            dic.Add(it.Key.ToString(), it.Value.ToString());
        }
    }

    public Type Make( string className )
    {
        var assemblyName = new AssemblyName("JsonProviderAssembly");
        var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll");
        var mb = moduleBuilder.DefineType(className, TypeAttributes.Class | TypeAttributes.Public, typeof(object));

        foreach (var it in dic)
        {
            var propName = it.Key;

            FieldBuilder customerNameBldr = mb.DefineField("_" + propName, typeof(string), FieldAttributes.Private);
            PropertyBuilder custNamePropBldr = mb.DefineProperty(propName, PropertyAttributes.HasDefault, typeof(string), null);
            MethodBuilder custNameGetPropMthdBldr =
                        mb.DefineMethod("get_" + propName,
                            MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                            typeof(string),
                            Type.EmptyTypes);
            ILGenerator custNameGetIL = custNameGetPropMthdBldr.GetILGenerator();
            custNameGetIL.Emit(OpCodes.Ldarg_0);
            custNameGetIL.Emit(OpCodes.Ldfld, customerNameBldr);
            custNameGetIL.Emit(OpCodes.Ret);
            MethodBuilder custNameSetPropMthdBldr =
                        mb.DefineMethod("set_" + propName,
                            MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                            null,
                            new Type[] { typeof(string) });
            ILGenerator custNameSetIL = custNameSetPropMthdBldr.GetILGenerator();
            custNameSetIL.Emit(OpCodes.Ldarg_0);
            custNameSetIL.Emit(OpCodes.Ldarg_1);
            custNameSetIL.Emit(OpCodes.Stfld, customerNameBldr);
            custNameSetIL.Emit(OpCodes.Ret);
            custNamePropBldr.SetGetMethod(custNameGetPropMthdBldr);
            custNamePropBldr.SetSetMethod(custNameSetPropMthdBldr);
        }
        Type t = mb.CreateType();
        assemblyBuilder.Save(assemblyName.Name + ".dll");
        return t;
    }
}

ModuleBuilder.CreateType でクラスを作った後で、AssemblyBuilder.Save で保存します。F# の TypeProvider の場合はこれがビルド時に行われるので、ビルド時のアセンブリと実行時のアセンブリが異なるので不整合が起ります。じゃあ、どちらも .NET Frameworkの環境であったり、ビルド時に敢えて Xamarin.Forms の PCL に合うようなアセンブリを衝くてやれば良いのだろう、と考えているのですが、これはまた後で実験します。

さて、AssemblyBuilder.Save で保存した DLL を、プロジェクトから参照設定すると作成した SampleJson クラスが使えます。一度、実行して DLL を作らないと駄目ってところが、結局のところ T4 と同じで、あまり意味がない。F# の TypeBuilder のように自動でインテリセンスが効けばいいんだけど。
ちなみに、作成した JsonProviderAssembly.dll を参照設定して、SampleJson のインスタンスを作ろうとすると DLL がロックされて書き込めないというデッドロックな状態になります。ビルド時にコピーする処理が必要ですね。
あと、実行しないとアセンブリが作られないので、T4 にして、ビルド時にアセンブリを作って後から参照するとかにしないと。

クラスを定義するためのいくつかの方法: C# プログラミング 再入門
http://dotnetcsharptips.seesaa.net/article/416983160.html

これを見る限り、CSharpCodeProvider を使って文字列から生成するほうが楽そうですね。が、Xamarin.Android からは Microsoft.CSharp.CSharpCodeProvider が見当たらないので、これはこれで。

カテゴリー: 開発 | ちょっと雑だが、C# で JsonProvider もどきを作る はコメントを受け付けていません

TypeBuilderを使って、既存のクラスにメソッドを生やす

継承可能なDynamicObjectを作ろうとしたが挫折中 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/8672

なところで、Xamarin.Forms からイベントを探索しているのはリフレクションを使っているなあ、ということが分かったので、じゃあ元のクラスに仮のメソッドをつけて無視すればいいのでは?と考えました。

XAML で、Clicked イベントがついていた時に

<Button Text=&quot;Click me&quot; Clicked=&quot;Button_Clicked&quot; />

↓なように、動的に Button_Click を生やしたいわけです。

public class MainPage : Xamarin.Forms.ContentPage
{
    private void Button_Click(object sender, EventArgs e)	
    {
    }
}

本来ならば、コンパイル時にメソッド名が決まっていればよいので、普通に ContentPage を継承してあらかじめコードで Button_Click を付けておけばよいのですが、XamlPreview のように XAML だけを送る場合は動的に Button_Click に作りたいのですよね。

実行時に Button_Click メソッドを作る

TypeBuilder.CreateType メソッド (System.Reflection.Emit)
https://msdn.microsoft.com/ja-jp/library/system.reflection.emit.typebuilder.createtype(v=vs.110).aspx

というのがあって実行時にクラスが作れます。
最初に断っておきますが、これは Xamarin.iOS では動きません。動かないので、XamlPreview の目的に達しないのですが、まあ、忘備録的に記録しておくというとで。Xamarin.Android 上では動くし、どうやら .NET Core 上でも動くので他にも応用が利きそうかなと。

public class PageGenerator : IPageGenerator
{
    public Type Create()
    {
        var assemblyName = new AssemblyName("dynamicassembly");
        var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);
        var tb = moduleBuilder.DefineType("DynamicContentPage", TypeAttributes.Class, typeof(Xamarin.Forms.ContentPage));
            
        MethodBuilder meth = tb.DefineMethod(
            "Button_Clicked",
            MethodAttributes.Public,
            typeof(void),
            new Type[] { typeof(object), typeof(EventArgs) });
        ILGenerator methIL = meth.GetILGenerator();
        methIL.Emit(OpCodes.Ret);

        Type t = tb.CreateType();
        return t;
    }
}

IPageGenerator を定義しているのは、PCL プロジェクト内では動かないので DependencyService.Get するためです。
動的にアセンブリを作って、クラスを作成しています。AssemblyBuilderAccess.Run を指定するとメモリ上で動きますね。DefineType メソッドで継承先に Xamarin.Forms.ContentPage を指定します。
メソッド名は、DefineMethod で指定して、中身は GetILGenerator で作るという感じ。

こうすると、ContentPage を継承した DynamicContentPage というクラスが動的にできます。
これをメインのほうで、

var tg = DependencyService.Get<IPageGenerator>();
Type t = tg.Create();
ContentPage page = Activator.CreateInstance(t) as ContentPage;

とすれば、無事 ContentPage オブジェクトとして使えます。DynamicContentPage クラス自体は動的に作ったものなので、プログラムを書いているときには存在しません。なので、当然インテリセンスとかは効きません。

これ、ビルド時にアセンブリに落として参照設定すれば、F# の TypeProvider と同じ動きになるんじゃないかなと思うんですが、どうなんでしょう?

メソッドの中身を Expression.CompileToMethod で書ける?

動的に作成した Button_Clicked ですが、IL なので、ちょっと面倒くさい。

ILGenerator methIL = meth.GetILGenerator();
methIL.Emit(OpCodes.Ret);

じゃあ、Expression を使って、Expression.Lambda で既存のメソッドを呼び出せば楽じゃないか?と思って作ったのがこれ。CompileToMethod を使うと IL を吐き出してくれます。

var mi = typeof(PageGenerator).GetMethod("Button_Clicked", new Type[] { typeof(object), typeof(EventArgs) });
var arg1 = Expression.Parameter(typeof(object));
var arg2 = Expression.Parameter(typeof(EventArgs));
var lambda = Expression.Lambda(Expression.Call(mi, arg1, arg2), arg1, arg2);
lambda.CompileToMethod(meth);

でも、なぜか Xamarin.Android 上では実行時に CompileToMethod でダンマリになるという感じでうまくいかない。
何かしたいというと、動的に作った Button_Clicked から、既存のコードを呼び出して、それをさらに Desktop のクライアントに送れたら便利かなと思った次第なのですが、CompileToMethod がうまくいかない。

Xamarin.iOS では動かない

iOS では動的コードが動かないので、Xamarin.iOS では動きません。ビルドは通るけど、実行時に AssemblyBuilder.DefineDynamicAssembly で落ちます。

iOS で Emit 絡みがダメなのが分かったので、XamlPreview では使えないのだけど、デスクトップ側でアセンブリができるのだから、F# の TypeProvider っぽいのができないかなぁと思案中…なので続く。

カテゴリー: 開発, Xamarin | TypeBuilderを使って、既存のクラスにメソッドを生やす はコメントを受け付けていません

継承可能なDynamicObjectを作ろうとしたが挫折中

Xamarin.Forms 用の超軽量プレビューアを作る | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/8669

で、XAMLにクリックイベントが入っていると XamlLoader がパースエラーになるので、そのイベントをうまい具合に無視しなければいけないのですが、じゃあ、もともとある ContentPage クラスに後からイベントを追加できたらうまくスルーできるのではないか?と思って、継承可能な DynamicObject を探していました。

正確に言えば、DynamicObject は継承可能なので、

public class DynamicViewModel : DynamicObject
{
    Dictionary<string, object> dic = new Dictionary<string, object>();

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        return dic.TryGetValue(binder.Name, out result);
    }
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        dic[binder.Name] = value;
        return true;
    }
}

な感じで DynamicObject を継承した ViewModel を作っておいて、後追いで次のようにプロパティを増やすことが可能です。

dynamic vm = new DynamicViewModel();
vm.Title = "Hello";
var title = vm.Title;

dynamic なので、インテリセンスは効かないけど、うまくくるめば XML や JSON をマッピングすることができます。ちなみに、Newtonsoft.Json.Linq.JObject を使うと、WPF の ViewModel としてそのまま使えます。何故か、Xamarin.Forms では使えないので、呼び出し方が微妙に違うのかなと。呼び出せるほうが不思議な感じがするのですが。.NET Framework と Profile259 の Runtime の違いかもしれません。

DynamicViewModel な方法は、ASP.NET の ViewBag にも使われているので割とポピュラーな手段です。詳細は、

メタプログラミング.NET | Kevin Hazzard, Jason Bock
https://www.amazon.co.jp/dp/4048867741

な本にも書いてあります。随分前だけど、

MVVMパターンでViewModelを楽に作る方法 – かずきのBlog@hatena
http://blog.okazuki.jp/entry/20100702/1278056325

なところで、MSDN マガジンへのリンクもあります。と言う訳で、じゃあ、DynamicObject を継承して ContentPage に後付けで Clicked なメソッドを生やすことができるんじゃないだろうか、と考えて、

public class SubPage : ContentPage, DynamicObject
{
    public SubPage() { }

    /*
    private void Button_Clicked(object sender, EventArgs e)
    {

    }
    */
    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        // 読み捨て
        System.Diagnostics.Debug.WriteLine("called: " + binder.Name);
        result = null;
        return true;
    }
}

なことを考えたのですが、ダメです。C# は多重継承ができないから、ContentPage と DynamicObject の両方を基底に持つことはできないんですね。じゃあ、どっちかをインターフェースにして、内部で再実装させればいいと思ったわけで、となると DynamicObject のほうをインターフェースにしたいですよね。ってことであれこれ探すとそれっぽいものがありました。

remi/MetaObject: Simple dynamic method invocation for your .NET objects
https://github.com/remi/MetaObject

IDynamicMetaObjectProvider インターフェースを付けて、内部的に再実装しようという試みです。

public class DynamicContnetPage : ContentPage, IDynamicMetaObjectProvider
{
    #region MetaObject
    public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression e)
    {
        return new MetaObject(e, this);
    }
    #endregion

    Dictionary<string, object>; dic = new Dictionary<string, object>();

    public virtual System.Collections.Generic.IEnumerable<string> GetDynamicMemberNames()
    {
        // return Value.GetDynamicMemberNames();
        return new string[] { };
    }

    public virtual bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        result = null;
        return true;
    }
    public virtual bool TryGetMember(GetMemberBinder binder, out object result)
    {
        return dic.TryGetValue(binder.Name, out result);
    }
    public virtual bool TrySetMember(SetMemberBinder binder, object value)
    {
        dic[binder.Name] = value;
        return true;
    }
}

こんな風に MetaObject を使っておくと、

public class SubPage : DynamicContnetPage
{
    public SubPage() { }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        // 読み捨て
        System.Diagnostics.Debug.WriteLine("called: " + binder.Name);
        result = null;
        return true;
    }
}

こんな風に、Clicked イベントやらを読み捨ててくれるはずです。中身で何か実装すれば、デバッグログとか通信っぽいものもできますね。このあたりは、実 DynamicObject のコードを見るといいのですが、中身的に IDynamicMetaObjectProvider インターフェースが DynamicMetaObject GetMetaObject(Expression parameter) を要求するのでメタデータを用意しておかないという仕組み&制限なのです。でもって、これが「式 Expression」を要求するというメタ構造になっていて、えらい大変なことになってます。

さて、これで万事解決と思いきや、いざコンパイルしてみると、MetaObject のコードがビルドできません。なんと、MetaObject は .NET Framework 専用なんですね。ああ、Xamarin.Forms の PCL は Profile259 なので .NET Runtime を使う訳なので、微妙に異なる訳です。仕方がないので、Runtime のほうに書き直そうかとしたら案の定 System.Reflection の中身が違うので、GetMethod を GetRuntimeMethod に直したりしながら、ええ、GetConstructor がないので、GetTypeInfo().DeclaredConstructors に変えてみたりと、あれこれとビルドが通るように修正。

で、なんとかビルドが通ったものを Xamarin.Forms の PCL に配置していざ、XamlLoader を動かすと、嗚呼、TryInvokeMember が呼び出される前に例外をはいて落ちてしまいます。どうやら、XAML に Clicked イベントを書くと XAML をパースするときに対応するメソッドを探してしまうらしいんですね。

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> Xamarin.Forms.Xaml.XamlParseException: Position 13:37. No method Button_Clicked found on type XamlPreview.SubPage
  at Xamarin.Forms.Xaml.ApplyPropertiesVisitor.SetPropertyValue (System.Object xamlelement, Xamarin.Forms.Xaml.XmlName propertyName, System.Object value, System.Object rootElement, Xamarin.Forms.Xaml.INode node, Xamarin.Forms.Xaml.HydratationContext context, System.Xml.IXmlLineInfo lineInfo) [0x000de] in C:\BuildAgent3\work\ca3766cfc22354a1\Xamarin.Forms.Xaml\ApplyPropertiesVisitor.cs:310 
  at Xamarin.Forms.Xaml.ApplyPropertiesVisitor.Visit (Xamarin.Forms.Xaml.ValueNode node, Xamarin.Forms.Xaml.INode parentNode) [0x00070] in C:\BuildAgent3\work\ca3766cfc22354a1\Xamarin.Forms.Xaml\ApplyPropertiesVisitor.cs:63 
...

ここで、どんな方法で探しているのかが不明(たぶんリフレクション?)なので、ちょっとこの先は解らず。仕方がないから Xamarin.Forms のコードを読むか、別な対策を立てるか思案中。

 

あった、

in ApplyPropertiesVisitor.cs で XamlParseException 例外を発生させている。

		static bool TryConnectEvent(object element, string localName, object value, object rootElement, IXmlLineInfo lineInfo, out Exception exception)
		{
			exception = null;

			var elementType = element.GetType();
			var eventInfo = elementType.GetRuntimeEvent(localName);
			var stringValue = value as string;

			if (eventInfo == null || IsNullOrEmpty(stringValue))
				return false;

			var methodInfo = rootElement.GetType().GetRuntimeMethods().FirstOrDefault(mi => mi.Name == (string)value);
			if (methodInfo == null) {
				exception = new XamlParseException($&quot;No method {value} found on type {rootElement.GetType()}&quot;, lineInfo);
				return false;
			}

			try {
				eventInfo.AddEventHandler(element, methodInfo.CreateDelegate(eventInfo.EventHandlerType, rootElement));
				return true;
			} catch (ArgumentException ae) {
				exception = new XamlParseException($&quot;Method {stringValue} does not have the correct signature&quot;, lineInfo, ae);
			}
			return false;
		}
カテゴリー: 開発, XAML, Xamarin | 継承可能なDynamicObjectを作ろうとしたが挫折中 はコメントを受け付けていません

Xamarin.Forms 用の超軽量プレビューアを作る

自前の LoadFromXaml ができたので、実機に XAML を送り込んで表示するプレビューアを作ってみます。
その昔、

moonmile/XFormsPreviewer: Dynamic loading XAML file of Xamarin.Forms
https://github.com/moonmile/XFormsPreviewer

なるものを作り始めたのだが、自前で XAML をパースしているので、Forms のバージョンアップに追随できないし、そのうちに本家から Xamarin.Forms Previewer が出たり、Xamarin Live Player が出たりして、頓挫&忘れておりました。
が、どうも、俺の思っているプレビューアと違う、と思っていた次第で、やっぱり軽量なものを作ってみようかと。

コード

moonmile/XFormsXamlDynamicLoad:
https://github.com/moonmile/XFormsXamlDynamicLoad

– src/XamlPreview 実機やエミュレータに仕込むプレビューアプリ
– src/XamlPreviewUp 実機へXAMLをPOSTするWPFアプリ

使い方

  1. XamlPreview をビルドして、エミュレーターや実機にインストール(デバッグ実行)します。
  2. XamlPreview は http://172.16.0.16:8080/ な感じで簡易HTTPサーバーしてます。

  1. XAML を送り込む XamlPreviewUP を起動します。
  2. IP を設定して、プレビューしたい XAML ファイルをドロップします。

すると、エミュレーター/実機のXamlPreviewの表示が切り替わります。

XAML を送り込むタイミングは、ファイルをドロップしたときと XamlPreviewUP のボタンを押したときなので、Visual Studio で XAML を修正した後にボタンを押して画面を更新すればよいでしょう。これは、そのうち更新状態をチェックして自動のアップロードするようにする予定。

制限

内部的に Xamarin.Forms.Xaml.XamlLoader クラスの Load メソッドを使っているので、XAML ファイルだけだとパースエラーになる場合があります。
例えば、Button のクリックイベント(Clicked)が書いてあるとエラーになるという状態。これ、わざわざイベント先のメソッドの存在をチェックしているらしく、所謂イベント系を書いてしまうとエラーになります。
これは、XAML を送るときにイベント系のものを削ってしまおうかなと。

Binding の記述はそのままでも大丈夫なので、デザイン時のデータバインディングクラスを作るとかして、対応したいかなと。特に ListView のデモとかにはよいだろうと。

利点

Xamarin.Forms Previewer や Xamarin Live Player よりも圧倒的に軽いです。これは PCL ライブラリとかビルドせずに、XAML だけ送って表示しているので、最初のデザインとかGridの調節とかにや良いかなと。あと、XAML の練習用ですね。

ちょっと面白いところでは、SliderBindingsPage.xaml が動きます。

XAML 内だけでバインディングする方法でスライダーを動かすと文字が回転します。このあたりの XAML は https://github.com/xamarin/xamarin-forms-samples からピックアップしています。

コードの解説は後日

カテゴリー: 開発, Xamarin | Xamarin.Forms 用の超軽量プレビューアを作る はコメントを受け付けていません

リフレクションを使ってXamarin.FormsにXAMLを動的ロードする

ふと、LoadFromXaml が public になれば、と思って探して、Xamarin.Forms のコードを見ていったら、

Load Xaml Dynamically At Runtime ? Xamarin Forums
https://forums.xamarin.com/discussion/87727/load-xaml-dynamically-at-runtime
Please make XamlLoader Public ? Xamarin Forums
https://forums.xamarin.com/discussion/87810/please-make-xamlloader-public

需要はあるようなないような。以前、動的にロードしたかったのは Xamarin Live Player 以前の頃だったので、既にあるからまあ特にいらんだろうという感じもするし、XAML をサーバーからダウンロードして切り替えることができたら、少しは違うかもしれないと思ったり。

サンプル

moonmile/XFormsXamlDynamicLoad: Xamarin.FormsでXAMLを動的にロードするサンプル
https://github.com/moonmile/XFormsXamlDynamicLoad

拡張メソッドを定義

ContentPageのLoadFromXamlメソッドが private ならば、自前で作ってしまえ、ってことで自前で作ってしまう。

static class ContentPageExtensions
{
    public static TXaml LoadFromXaml<TXaml>(this TXaml view, string xaml)
    {
        Load(view, xaml);
        return view;
    }
    private static void Load(object view, string xaml)
    {
        var t = Type.GetType(&quot;Xamarin.Forms.Xaml.XamlLoader, Xamarin.Forms.Xaml&quot;);
        var mi = t.GetRuntimeMethod(&quot;Load&quot;, new Type[] { typeof(object), typeof(string) });
        var obj = mi.Invoke(null, new object[] { view, xaml });
        return;
    }
}

自前のLoadFromXamlから、本物のXamarin.Forms.Xaml.XamlLoader.Loadを呼び出す。実は、XamlLoaderクラスはinternalなので呼び出せないのだが、Type.GetTypeでロードして、GetRuntimeMethodを使うと呼び出せるんですね。

PrivateObject クラス?(Microsoft.VisualStudio.TestTools.UnitTesting)
https://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.testtools.unittesting.privateobject.aspx

と同じことをやります。

メインページから遷移するときに、自前で XAML をロードさせます。コード内に書いちゃうとリソースで XAML にしたほうが楽だろう(実際そうだし)と思うのですが、似たような感じでネット上から XAML をダウンロードすることも可能ということで。

private void Button_Clicked(object sender, EventArgs e)
{
    var xaml = @&quot;<?xml version=&quot;&quot;1.0&quot;&quot; encoding=&quot;&quot;utf-8&quot;&quot; ?>
<ContentPage xmlns=&quot;&quot;http://xamarin.com/schemas/2014/forms&quot;&quot;
        xmlns:x=&quot;&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;&quot;>
<StackLayout>
<Label Text=&quot;&quot;Hello Sub Page&quot;&quot; />
<Button Text=&quot;&quot;click me!&quot;&quot; Clicked=&quot;&quot;Button_Clicked&quot;&quot; />
<Label Text=&quot;&quot;{Binding Count, StringFormat='{0} clicked'}&quot;&quot; />
</StackLayout>
</ContentPage>
&quot;;
    /// XAML を動的にロードする
    this.Navigation.PushAsync(new SubPage().LoadFromXaml(xaml));
}

SubPage クラスのほうは MVVM も効くので、ViewModel クラスだけで取り廻して XAML のほうは動的に切り替えることもできるかな、と。

public class SubPageViewModel : ObservableObject
{
    private int count = 0;
    public int Count
    {
        get { return count; }
        set { SetProperty(ref count, value, nameof(Count)); }
    }
}
public class SubPage : ContentPage
{
    public SubPage()
    {
        vm = new SubPageViewModel();
        this.BindingContext = vm;
    }
    SubPageViewModel vm;
    private void Button_Clicked(object sender, EventArgs e)
    {
        vm.Count++;
    }
}

実行

カテゴリー: 開発, XAML, Xamarin | リフレクションを使ってXamarin.FormsにXAMLを動的ロードする はコメントを受け付けていません

F#でXamarin.Formsを使う

いつからできるようになったんだっけ?

Petzold Book Blog – Writing Xamarin.Forms Apps in F#
http://www.charlespetzold.com/blog/2015/10/Writing-Xamarin-Forms-Apps-in-FSharp.html

なところなので、実は随分前からある。

で、Visual Studio for Mac の「Blank Forms App」で F# が選べるようになっていたので試してみる。ちなみに、Windows のほうの Visual Studio 2017 には F# のテンプレートがないので、Mac で作ったものを Windows 側にコピーしている。

テンプレートが少しおかしいらしく、Android のほうの Xamarin.Forms が入らない(入れようとしてエラーになっている)ので手動で入れる。

Mac のほうで作るので UWP のプロジェクトはない。
きちんと *.xaml があるので、XAML がロードできる。

type SampleXFormsFPage() = 
    inherit ContentPage()
    let xaml = base.LoadFromXaml(typeof<SampleXFormsFPage>)

C# だと、自動生成された *.g.cs ファイルにLoadFromXamlがあって、内部的にクラスとXAMLをマッチングさせる。で、F# の場合は *.g.fs を生成しないので、これを直接使うという訳。

いわゆる x:Name がプロパティにマッピングされないので、自前で FindByName を呼び出す。OnAddメソ\ッドは、動的にリフレクションを使っているのか、AddHandler を使わずにマッピングされる。UWP の場合は、Click に対応するイベントコードが生成されるが、Xamarin.Forms の場合は、イベントが生成されないのでビルド時にはチェックされず、実行時に対応するイベントがないと例外が発生する。これは C# も F# も同じ。

namespace SampleXFormsF

open Xamarin.Forms
open Xamarin.Forms.Xaml
open System

type TodoItem() =
    member val Id = &quot;&quot; with get, set 
    member val Name = &quot;&quot; with get, set
    member val Done = false with get, set

type SampleXFormsFPage() = 
    inherit ContentPage()
    let _ = base.LoadFromXaml(typeof<SampleXFormsFPage>)
    let todoList = base.FindByName<ListView>(&quot;todoList&quot;)
    let newItemName = base.FindByName<Entry>(&quot;newItemName&quot;)
    let items = new System.Collections.ObjectModel.ObservableCollection<TodoItem>()

    do
        todoList.ItemsSource <- items

    override this.OnAppearing() = 
        base.OnAppearing()

    member this.OnAdd( sender : obj, e : EventArgs ) =
            let item = new TodoItem()
            item.Id <- Guid.NewGuid().ToString()
            item.Name <- newItemName.Text
            items.Add( item )

    member this.OnSelected( sender : obj, e : SelectedItemChangedEventArgs ) = ()
    member this.OnRefresh( sender : obj, e : EventArgs ) =  ()
    member this.OnComplete( sender : obj, e : EventArgs ) =  ()

XAML はこんな感じ。もともと、Mobile App のクイックスタートのコードを持ってきているので、後から Azure に対応させる。

<?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?>
<ContentPage xmlns=&quot;http://xamarin.com/schemas/2014/forms&quot; xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot; xmlns:local=&quot;clr-namespace:SampleXFormsF&quot; x:Class=&quot;SampleXFormsF.SampleXFormsFPage&quot;>
    <Grid RowSpacing=&quot;0&quot;>
        <Grid.RowDefinitions>
            <RowDefinition Height=&quot;Auto&quot; />
            <RowDefinition Height=&quot;*&quot; />
        </Grid.RowDefinitions>
        <ActivityIndicator Grid.RowSpan=&quot;2&quot; HorizontalOptions=&quot;Center&quot; VerticalOptions=&quot;Center&quot; IsVisible=&quot;False&quot; IsEnabled=&quot;True&quot; x:Name=&quot;syncIndicator&quot;/>
        <StackLayout Grid.Row=&quot;0&quot; BackgroundColor=&quot;#5ABAFF&quot; Padding=&quot;10,30,10,5&quot;>
            <Label TextColor=&quot;#555555&quot; Text=&quot;Azure App Service&quot; />
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition Width=&quot;Auto&quot;/>
                </Grid.ColumnDefinitions>
                <Entry x:Name=&quot;newItemName&quot; Placeholder=&quot;Item name&quot; />
                <StackLayout x:Name=&quot;buttonsPanel&quot; Grid.Column=&quot;1&quot; Orientation=&quot;Horizontal&quot; HorizontalOptions=&quot;StartAndExpand&quot;>
                    <Button Text=&quot;+&quot; MinimumHeightRequest=&quot;30&quot; Clicked=&quot;OnAdd&quot; />
                </StackLayout>
            </Grid>
        </StackLayout>
        <ListView x:Name=&quot;todoList&quot; ItemSelected=&quot;OnSelected&quot; IsPullToRefreshEnabled=&quot;true&quot; Refreshing=&quot;OnRefresh&quot; Grid.Row=&quot;1&quot;>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <ViewCell.ContextActions>
                            <MenuItem Clicked=&quot;OnComplete&quot; Text=&quot;Complete&quot; CommandParameter=&quot;{Binding .}&quot;/>
                        </ViewCell.ContextActions>
                        <StackLayout HorizontalOptions=&quot;StartAndExpand&quot; Orientation=&quot;Horizontal&quot; Padding=&quot;15,5,0,0&quot;>
                            <StackLayout Padding=&quot;5,0,0,0&quot; VerticalOptions=&quot;StartAndExpand&quot; Orientation=&quot;Vertical&quot;>
                                <Label Text=&quot;{Binding Name}&quot; />
                            </StackLayout>
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</ContentPage>

実行してみる


ちなみに、フロントが C# でも良いならば、適当な UWP プロジェクトを取ってきて、F# の PCL を参照設定することで動作できる。

カテゴリー: 開発, F#, Xamarin | F#でXamarin.Formsを使う はコメントを受け付けていません

Xamarin.Forms で .NET Standard を利用してファイルアクセスするまで

Xamarin.Forms プロジェクトのPCLを .NET Standard 化する方法を調べていくと、Visual Studio 2017 のリリース前だったりするので、project.json の変更があって「あれ?」となってしまうので、ちょっと記録的に。
ちなみに、この記事も半年ぐらい経つと意味がなくなるような気がするので、まあ、そのときはそのときで。

Xamarin.Formsでプロジェクトを作る

普通にXamarin.FormsでPCLを使ったプロジェクトを作ります。

Visual Studio 2017 の場合は、コード共有で「ポータブルクラスライブラリ」のほうを選択。

.NET Standard のクラスライブラリを追加する

[Visual C#]→[.NET Standard]で、「クラスライブラリ(.NET Stanrdard)」を追加

.NET Standardのプロジェクトを編集して、PackageTargetFallbackの部分を追加。これがないと、NuGetからXamarin.Formsを取り込めません。

<Project Sdk=&quot;Microsoft.NET.Sdk&quot;>
  <PropertyGroup>
    <TargetFramework>netstandard1.6</TargetFramework>
    <PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
  </PropertyGroup>

その後で、NuGetからXamarin.Formsをインストールします。

PCLプロジェクトからファイルをコピー

ソリューションエクスプローラで、「App.xaml」と「MainPage.xaml」をマウスでドラッグして、.NET Standardのプロジェクトにコピーします。
csprojファイルを開いて、ItemGroupのところをコピーしてもよいです。

Properties/AssemblyInfo.cs ファイルだけはコピーしません。内部的に、AssemblyTitle やらが作らている模様。

ちなみに、.NET Standardプロジェクトを追加したあとで、PCLプロジェクトをアンロードすると二度とロードできないという現象が発生します。なんだかなー。そういうときは、一度、.NET Standardプロジェクトを外して、PCLプロジェクトをロードします。

この手順を逆にして、.NET Standardプロジェクトのcsprojの内容を、PCLプロジェクトのほうにコピーしてもOKです。

Xamarin.iOS/Adnroidプロジェクトの参照を変更する

Xamarin.iOS/AdnroidプロジェクトからPCLプロジェクトが参照されているところを、.NET Standardプロジェクトのほうに変更します。
と、同時に Xamarin.Forms のバージョンも合わせておきます。

これでやっとこさ、Xamarin.Formsで.NET Standardが使える状態が完了です。まあ、ここまで作れたものをXamarin.Formsのプロジェクトテンプレートとして置いておけばよいので、一度作れると後は楽かも。

試しにファイルアクセスさせる

MainPage.xaml をこんな感じで修正しておいて、

<StackLayout>
    <Label Text=&quot;Welcome to Xamarin Forms!&quot; />
    <Button Text=&quot;save&quot; Clicked=&quot;Save_Clicked&quot; ></Button>
    <Button Text=&quot;load&quot; Clicked=&quot;Load_Clicked&quot; ></Button>
    <Label x:Name=&quot;text1&quot;  Text=&quot;output&quot; />
</StackLayout>

Save/Loadボタンをクリックしたときのイベントがこんな感じ。

private async void Save_Clicked(object sender, EventArgs e)
{
    var path = "/data/user/0/App18.Android/files/";
    var st = System.IO.File.Open(path + "sample.txt", FileMode.OpenOrCreate);
    using (var sw = new StreamWriter(st))
    {
        await sw.WriteLineAsync("sample data");
    }
}

private async void Load_Clicked(object sender, EventArgs e)
{
    var path = "/data/user/0/App18.Android/files/";
    var st = System.IO.File.Open(path + "sample.txt", FileMode.Open);
    using (var sr = new StreamReader(st))
    {
        var text = await sr.ReadToEndAsync();
        text1.Text = text;
    }
}

Xamarim.iOS/Androidであれば、「System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments)」と書くところが、パスを直書きになっているのがアレですね。iOS/Androidの場合分けが必要でしょう。どうやって書くのか判りませんが。
Xamarin.Forms の場合は、System.IO.Fileがないので、これが使えるということは無事 .NET Standard 化しているということです。

Androidエミュレータで実行すると、こんな感じになります。

じゃあ、PCLは.NET Standardに完全に置き換わるのか?

standard/docs/netstandard-20 at master ・ dotnet/standard
https://github.com/dotnet/standard/tree/master/docs/netstandard-20

を見る限り、「No PCL left behind. Dakota can consume a PCL based assembly from a .NET Standard-based library without having to jump through hoops.」とあるので、生のままでPCLを使うことは少なくなるんでしょう。少なくともProfileの順列組み合わせ状態は激減するかなと。現状、あまり意味がないし。
その後は、.NET Standard が肥大化しちゃうか、機種依存な機能が漏れてしまうかのところなんだけど、いまのところXamarin.Formsに関しては下回りのPCLは必須な感じがします。

カテゴリー: 開発, Xamarin | Xamarin.Forms で .NET Standard を利用してファイルアクセスするまで はコメントを受け付けていません

Azure 上の .NET Core では DbSet の Join ができない

ひどくピンポイントかつ3時間ほどハマってしまったので、メモ書き的に残しておく。

環境は、

  • ASP.NET Core
  • .NET Core 1.1
  • EF Core を利用

ASP.NET Core で Web API を作っていて、ローカルの .NET Core で LINQ の Join が動いていたのだが、いざ Azure にアップロードして動作させると、Join 部分がうまくいかなくて意味不明なエラーを返した。

var cnt1 = (
        from ti in _context.TicketView
        join st in _context.Status on ti.Status_Id equals st.Id
        where ti.ProjectId == project.Id &&
                ti.Tracker_Id == tr.Id &&
                st.IsClosed == false
        select ti.Id
    ).Count();

とあるところで、こんな風に TicketView と Statues テーブルを JOIN させた。何をやりたかったかというと、Statues テーブルの IsClosed フラグを見て、その数をカウントしたかったのだ。これを、ローカル PC の ASP.NET Core で動作させると、するすると動いたので、そのまま Azure に乗せてみると…

テーブル構造が違っているからマイグレーションしろ、

というお達しが来る。最初は、エラーの意味が解らなくて四苦八苦したのだが、少しずつ障害を絞り込んでいくとここの JOIN 部分に至った。この JOIN を消してしまって、var cnt1 = 0 とすると動作することが分かったので、あれこれやって、

var status = _context.Status.OrderBy(x =&gt; x.Position).ToList();
var ticketviews = _context.TicketView.Where(x =&gt; x.ProjectId == project.Id).ToList();

var cnt1 = (from ti in ticketviews
            join st in status on ti.Status_Id equals st.Id 
            where ti.ProjectId == project.Id &&
                    ti.Tracker_Id == tr.Id &&
                    st.IsClosed == false
            select ti.Id).Count();

な風に、一度 List に取り込んでから JOIN するとうまく動くようになった。

LINQ を使ってジェネリックの List 同士を Join させる分には大丈夫と解っているのだが、_context.Status のほう(DbSet<Status> などで DI されている)の Join の動作が不明である。もともと、ローカル PC で DbSet 同士の Join ができちゃうのがおかしいのか、それとも Azure 上の .NET Core で DbSet 同士の Join ができないのがおかしいのか。

どちらにせよ、同じコードをローカル PC と Azure 上で動かしたときに、挙動が違うのは「おかしい」ので、ここは調べないといけない。これ、Linux 上の .NET Core だとどうなのだろうか? 後で調べるか思案中。

追記 2017/05/11

Ubuntu に donet 1.0.3 をインストールして構築してみると、上の Join が動くようになったので、Azure 上だけがダメな模様。

Azure の App Service のコンソールでバージョンを調べると doetnet コマンド自体が 1.0.1 、.net core のバージョンが Version  : 1.1.0-preview1-001100-00 になっているので、この違いと思われる。ちなみに、ローカルPC の場合は、1.1.0 になっている。この微妙な差(Azure のほうが previewのままなのが)原因らしい。

Build 2017 の微妙な時期なので、これが終わると 2.0 にアップデートされるかもしれん。

追記 2017/05/19

現時点で、.NET Command Line Tools (2.0.0-preview1-005977)  になっているので、join が正常に動いている。やったー。

カテゴリー: Azure, OpenCCPM | Azure 上の .NET Core では DbSet の Join ができない はコメントを受け付けていません

オーケストラに学ぶプロジェクトマネジメント

さて、やっとこさ書き上げて発売されたのでブログも再開。

[amazonjs asin=”4798050024″ locale=”JP” title=”成功するチームの作り方 オーケストラに学ぶプロジェクトマネジメント”]

目次

第1章 コンダクターとプロジェクトマネジメント
1 チームにおけるコンダクター(指揮者)の役目
2 プロフェッショナルな奏者≒プロフェッショナルなエンジニア
3 ハーモニー(調和)とは何か
4 チームが成し遂げる作品(製品)は何か

第2章 プロジェクト計画、プランニングの伝達
1 計画駆動とアジャイル開発とオーケストレーションと
2 形作る作品をイメージで共有する
3 どうやって形作っていくのか
4 三面図(WBS、PERT、ガントチャート)
5 CCPMと焦らない心

第3章 プロジェクトの構成員
1 新人が持っていない能力は何か
2 中途採用が持っている能力は何か
3 プロフェッショナルなエンジニア/プログラマとは
4 コンダクターとプロジェクトメンバーの関係
5 客演という外注

第4章 メンバーの技量
1 メンバーの能力を相互に知る
2 メンバーの能力を相互に尊重する
3 見極めに利用できるもの(ブログ、GitHub、資格)
4 「一緒に仕事ができる」条件を並べる

第5章 継続的な努力
1 チームに参加する時点が最低の実力
2 プロジェクトが終わった時点で最高点に達する
3 「Google 20%ルール」の本来の意味
4 「40時間勤務」の本来の意味
5 ツールを残して次回に備える

第6章 先人の知恵を活用する
1 新しいことを安易に取り入れない理由
2 暗黙知と形式知
3 UMLとパターンランゲージ
4 ISO9001と品質工学
5 温故知新の上にある新しい技術

第7章 現実を計測する
1 難破船のように漂わないために羅針盤を使う
2 現実に適応するためにハンドルを使う
3 希望が挫けないために航海日誌を書く
4 進捗率の自動計測とTDD
5 EVの本来の使い方
6 下降する進捗グラフ(バーンダウンチャート)

第8章 変化は好きですか?

1 世の中は思い通りにはいかない
2 世の中は変化ばかりではない
3 効果的な変化とは≒レバレッジ
4 計画駆動と要件定義
5 アジャイル開発とTOC
[コラム]すべてに目を光らせるために

第9章 仕事とバランス
1 チームでは何が有利か
2 チームでは何が不利か
3 「刺身システム」と「のりしろ」
4 マリッジリスクに対応する「ゆとり」
5 「ゆとり」の時間を何に活用するのか

第10章 ドロップアウトの条件
1 チームが悲鳴を上げる時
2 「排除すべき人」を排除する
3 ゼロ生産者とマイナス生産者に対応する
4 決断する「閾値」を決めておく
[コラム]戦略と戦術の違い

第11章 インセンティブと報酬
1 鞭のない「飴」
2 プロを雇うための費用を算出する
3 インセンティブを提案する
4 成果にのみ「報酬」を与える
[コラム]努力をムダにしない

第12章 差異と反復
1 ジル・ドゥルーズの『差異と反復』
2 本能で違いを読み取れ—差異
3 繰り返しで拡張しろ—反復
4 リスク管理とフェールセーフ
5 失敗学と危険な匂い

カテゴリー: 開発, プロジェクト管理 | オーケストラに学ぶプロジェクトマネジメント はコメントを受け付けていません