くりーむわーかー

プログラムとか。作ってて ・試しててハマった事など。誰かのお役に立てば幸いかと。 その他、いろいろエトセトラ。。。

2016年05月

.net MVCでSignalR

.net MVCでSignalRを使う。ちょっとはまったりもしたのでメモメモ。

とりあえず導入は、Nugetで「SignalR」をインストール。そしたらStartupのConfigurationしてる奴に以下を追加。

public void Configuration(IAppBuilder app)
{
    app.MapSignalR();//←これを追加
}

そしたら、Hubを作る。どこでもいいっぽいけど、Hubsってフォルダを作ってやった場合。

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace Hoge.Hubs
{
    [HubName("hogehub")]
    public class HogeHub : Hub
    {
        public void Send(string text)
        {
            Clients.All.HogeShortInfo(text);
        }
    }
}

次はView側。↓の感じ。コメント送信したら皆に飛ぶ感じのよくあるやつ。


<div>
    <input type="text" value="" id="message"/><input type="button" id="sendBtn" value="Send"/>
</div>
<div id="messageDiv">

</div>

<script src="~/Scripts/jquery.signalR-2.2.0.min.js">
<script src="/signalr/hubs">
<script type="text/javascript">
    $(function () {
        var echo = $.connection.hogehub;
        echo.on("HogeShortInfo", function (text) {
            $('#messageDiv').append(text);
        });
        $.connection.hub.start().done(function () {
            $('#sendBtn').click(function () {
                var msg = $('#message').val();
                echo.server.send(msg).done(function () {
                    $('#message').val("");
                });
            });
        });
    });
</script>

一個はまったのが、「signalr/hubs」。この子は動的に作られるらしく探しても出てこない。 ってとこまではいいんだけど、作られる階層がサイトのトップらしく、少し階層いったところのページでやる場合は「src="/signalr/hubs"」って頭に"/"をつけておかないと読み込んでくれない。大部分のサンプルは"signalr/hubs"ってなってるのでちょっとはまる。。。

ついで、小文字と大文字。基本的に小文字しか使えないと思ってたほうがいいのかしら。。。

で、書き方は何種類かあるっぽい。クライアント側のメソッドを定義するのは↓の2つの感じ。

        echo.on("HogeShortInfo", function (text) {
            $('#messageDiv').append(text);
        });
        echo.client.HogeShortInfo= function (text) {
            $('#messageDiv').append(text);
        });

個人的には上の書き方の方が、割り当ててますって感じがして好き。

あと、$.connection.hub.start().done()の中にイベントハンドラ入れとかないと、 接続する前にボタンとか押されてダメになるらしい。試してないけど作った人がそう言ってんだからそうしておきましょう。

って、ここまでは割と見かける。このやり方はチャットみたいな送信って押してみんなにいくみたいな場合。そーじゃなくて、サーバ側から何らかのイベント時に(Hubの外から)全体に送信したい場合はクライアントの呼び出しを下の感じで書く。

var hubContext = GlobalHost.ConnectionManager.GetHubContext<Hubs.HogeHub>();
hubContext.Clients.All.HogeShortInfo("なんか周知");

HogeShortInfoのところは、クライアントで割り当てた関数名を指定する感じ。↑のやつはコントローラーでもモデル側でも入れられるけど、↓のusingが必要。

using Microsoft.AspNet.SignalR;

で、サーバ側から任意のタイミングでクライアントの画面にメッセージ表示するみたいなのが↑ので出来るようになるんだけど、クライアント側のJavaScriptでもちょっとはまった。

    $(function () {
        var echo = $.connection.hogehub;
        echo.on("HogeShortInfo", function (text) {
            $('#messageDiv').append(text);
        });
        $.connection.hub.start();//←これが必須!!!
    });

「$.connection.hub.start();」これを入れないと動かない。

あと、サーバから返すデータは普通にJsonっぽいので↓の感じのオブジェクト作って返してあげてクライアント側で使ったりしやすい。

[System.Runtime.Serialization.DataContract]
public class TestHubJsonData
{
    [System.Runtime.Serialization.DataMember()]
    public int recid = 0;
    [System.Runtime.Serialization.DataMember()]
    public string state = "";
    [System.Runtime.Serialization.DataMember()]
    public string date = "";
}

下の感じで使用。オブジェクトの配列で返してもよきかな。

echo.on("TestDisp", function (data) {
    alert(data.date);
})

C#のラムダ式まとめ

あんま慣れてないのでいっつも忘れるのでメモ。

public void test()
{
    //delegate使って匿名メソッドを定義
    Func<int, int> x = delegate (int n) { return n * 10; };
    Console.WriteLine(x(1));

    //↑をラムダ式で
    x = (n) => { return n * 20; };
    Console.WriteLine(x(1));

    //↑をラムダ式でさらに簡素に
    x = (n => n * 30);
    Console.WriteLine(x(1));

    //中身の匿名関数をラムダ式でその場で定義して var に入れる
    var y = new Func<int, int>(n => { return n * 40; });
    Console.WriteLine(y(1));

    //引数が複数の場合のdelegate
    Func<int, int, int> xx = delegate (int n, int nn) { return n * nn; };
    Console.WriteLine(xx(1,10));

    //引数が複数の場合のラムダ式
    xx = (n, nn) => { return n * nn; };
    Console.WriteLine(xx(1, 10));

    //中身の匿名関数をラムダ式でその場で定義して var に入れる 引数複数版
    var yy = new Func<int, int, int>((n,nn) => { return n * nn; });
    Console.WriteLine(yy(1, 10));

    //引数無しの場合のdelegate
    Func<int> xxx = delegate () { return 20; };
    Console.WriteLine(xxx());

    //引数が複数の場合のラムダ式
    xxx = () => { return 20; };
    Console.WriteLine(xxx());

    //中身の匿名関数をラムダ式でその場で定義して var に入れる 引数複数版
    var yyy = new Func<int>(() => { return 20; });
    Console.WriteLine(yyy());


    //Actionの場合 引数1個
    Action<int> a = delegate (int n) { Console.WriteLine(n); };
    a(30);
    a = (n) => Console.WriteLine(n);
    a(30);
    a = n => Console.WriteLine(n);
    a(30);
    //var b = new Action<int>(n => Console.WriteLine(n));
    var b = new Action<int>( (n) => { Console.WriteLine(n); });
    b(30);


    //Actionの場合 引数2個
    Action<int,int> aa = delegate (int n,int nn) { Console.WriteLine(n*nn); };
    aa(4, 10);
    aa = (n, nn) => { Console.WriteLine(n * nn); };
    aa(4, 10);
    var bb = new Action<int, int>((n, nn) => { Console.WriteLine(n * nn); });
    bb(4, 10);

    //Actionの場合 引数無し
    Action aaa = delegate () { Console.WriteLine(0); };
    aaa();
    aaa = () => { Console.WriteLine(0); };
    aaa();
    aaa = () => Console.WriteLine(0);
    aaa();
    var bbb = new Action( () => { Console.WriteLine(0); } );
    bbb();
}

C#でutf-16の外字領域の文字があるか調べる

最近、文字コードで色々あった。とりあえず外字。utf-16の文字列内にUnicodeの私用領域の文字が含まれているか調べる正規表現。

public void hoge(string wkStr)
{
	string regexp = @"[\uE000-\uF8FF]";
	if(System.Text.RegularExpressions.Regex.IsMatch(wkStr,reqexp))
	{
	     Console.WriteLine("外字");
	}
}

そーいえば、バイナリエディタで見るとバイトの並びって逆になるのだろうか。いまいち分かってない。 エディタにもよるのかしら。

Bzで見ると「E0 00 F8 FF」⇒「00 E0 FF F8」で見える。

C#で文字列の16進数をbyteに変換する

なんか忘れるのと、調べると、そのままやると落ちる情報が出てくるので特筆。

//直指定の場合
byte b1 = 0xF0;
//文字列の16進数を変換する場合
byte b2 = Convert.ToByte("F1", 16);

調べると「Convert.ToByte("05");」みたいなのが割と出てくる。これ多分10進数で変換してると思う。 なので、引数で16進数なのよっていう指定が必要。

で、最近これが必要だったのが、EBCDICのデータもらっちゃったから。また、古い文字コードだ。まぁ、ホストだったら現役バリバリでしょーけども。

幸い数字と空白スペースのみのデータだったのでバイナリの変換表をテケトーに作ってSJISにしてやった。これで日本語とか入ってきたり、外字なんかが入ってるともー最悪な状況になる。助かった。そーいえば昔、EBCDICの文字コードをいろいろ調べてみたことがあったなーとか遠い目をしてみる。みんな好き勝手やりすぎ。ちなみに↓が変換のソース。


Dictionary<byte, byte> mapDic = new Dictionary<byte, byte>();

public void init()
{
mapDic.Add(Convert.ToByte("F0", 16), Convert.ToByte("30", 16));
mapDic.Add(Convert.ToByte("F1", 16), Convert.ToByte("31", 16));
mapDic.Add(Convert.ToByte("F2", 16), Convert.ToByte("32", 16));
mapDic.Add(Convert.ToByte("F3", 16), Convert.ToByte("33", 16));
mapDic.Add(Convert.ToByte("F4", 16), Convert.ToByte("34", 16));
mapDic.Add(Convert.ToByte("F5", 16), Convert.ToByte("35", 16));
mapDic.Add(Convert.ToByte("F6", 16), Convert.ToByte("36", 16));
mapDic.Add(Convert.ToByte("F7", 16), Convert.ToByte("37", 16));
mapDic.Add(Convert.ToByte("F8", 16), Convert.ToByte("38", 16));
mapDic.Add(Convert.ToByte("F9", 16), Convert.ToByte("39", 16));
mapDic.Add(Convert.ToByte("40", 16), Convert.ToByte("20", 16));
}
public byte convCharSet2(byte b)
{
    byte result = 0x3F;//?の文字コード
    if (mapDic.ContainsKey(b))
        result = mapDic[b];
    return result;
}


//下の感じで変換
bytedata = convCharSet2(bytedata);

上だとコードに変換表埋め込んでるけど、実際はテキストファイルとかにしておいて読み込む感じ。

C#で共有フォルダ(UNCで)にアクセスする

UNCなパス指定でネットワーク越しのファイルアクセスを認証からC#のプログラムからやりたい場合。 ドライブを登録するとか、あらかじめ、プログラム動かす端末から手でつないでおくといけるとかあるけども、プログラム側からちゃんと通しておきたい場合。とりあえず、C#の標準機能?としてはそういったものは無いらしい。WinAPIを呼んでやるしかない様子。↓ソース。どこかのサイトに載ってたやつほぼそのままだったと思うだけど、どこだったか覚えていない。。。


using System.Runtime.InteropServices;
class Hoge
{
        //接続まわり
        [DllImport("mpr.dll", EntryPoint = "WNetCancelConnection2", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
        private static extern int WNetCancelConnection2(string lpName, Int32 dwFlags, bool fForce);
        [DllImport("mpr.dll", EntryPoint = "WNetAddConnection2", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
        private static extern int WNetAddConnection2(ref NETRESOURCE lpNetResource, string lpPassword, string lpUsername, Int32 dwFlags);

        //APIに渡す接続情報
        [StructLayout(LayoutKind.Sequential)]
        public struct NETRESOURCE
        {
            public int dwScope;//列挙の範囲
            public int dwType;//リソースタイプ
            public int dwDisplayType;//表示オブジェクト
            public int dwUsage;//リソースの使用方法
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpLocalName;//ローカルデバイス名。使わないならNULL。
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpRemoteName;//リモートネットワーク名。使わないならNULL
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpComment;//ネットワーク内の提供者に提供された文字列
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpProvider;//リソースを所有しているプロバイダ名
        }

        public string ServerUNC { get; set; }
        public string ServerId { get; set; }
        public string ServerPass { get; set; }

       public void ConnectServer()
       {
            //接続情報
            ServerUNC  = @"\\192.168.1.1\x$";//←ドライブ指定でもいける
            ServerId   = "HimitsuNoId";//試してないけど"hoge\fugaid"みたいなドメイン付きも行けるとおもう。
            ServerPass = "HimitsuNoPass";
            
            NETRESOURCE netResource   = new NETRESOURCE();
            netResource.dwScope       = 0;
            netResource.dwType        = 1;
            netResource.dwDisplayType = 0;
            netResource.dwUsage       = 0;
            netResource.lpLocalName   = "";
            netResource.lpRemoteName  = ServerUNC;
            netResource.lpProvider    = "";
            
            int ret = 0;
            try
            {
                //既に接続がある場合は一回接続削除
                ret = WNetCancelConnection2(ServerUNC, 0, true);
                //UNC名で接続
                ret = WNetAddConnection2(ref netResource, ServerPass, ServerId, 0);
            }
            catch
            {
                //何かダメ
            }
            
            
            //↑が通れば後は普通にアクセス出来る
            DirectoryInfo di = new DirectoryInfo(Path.Combine(ServerUNC,@"hoge\fuga"));
            foreach(FileInfo tmp in di.GetFiles())
            {
            	Console.WriteLine(tmp.FullName);
            }
       }
}

UNCの指定はエクスプローラで使えるならホスト名でも基本的に問題はないはず。ただ、名前解決が異常に遅いとか色々問題が出ることが多いので、個人的にはIPでやってる。正直、サーバのIP変わることなんて現実的に皆無なので。っていうか今まであったことがない。その前にリース切れるか更改するでしょ。

2016/07/01追記

プログラムを動かすユーザーによってうまくいかない事もある。特にWebアプリでやる場合、アプリケーションプールのIDがデフォルトのままだとほぼ確実にうまくいかない。IISで該当のアプリケーションプールをAdmin権限のユーザにするか、セキュリティ的にNGならアプリケーションプール用のユーザーをちゃんと作ってやらないとつなげない。

C#のラムダ式で複数項目のGroupByしてCountする

タイトル通り。ちなみにいまだに下の感じのメソッドチェーンがラムダ式と言っていいのかわかってなかったりする。。。今度ちゃんとやろ。

foreach (var tmp in wkListObj.Where(n => n.DateVal == wkDate)	
                             .GroupBy(n=> new { n.DateVal,n.HogeVal,n.FugaVal})
                             .Select(g=> new { DateVal = g.Key.DateVal,HogeVal = g.Key.HogeVal,FugaVal = g.Key.FugaVal,件数 = g.Count()})
)
{
    Console.WriteLine("{0}:{1}","DateVal",tmp.DateVal);
    Console.WriteLine("{0}:{1}","HogeVal",tmp.HogeVal);
    Console.WriteLine("{0}:{1}","FugaVal",tmp.FugaVal);
    Console.WriteLine("{0}:{1}","件数",tmp.件数);
}

javascript D3使って集計とか

D3使ってオブジェクト内の集計をやりたくなった。ちょっとだけ複雑なオブジェクトでやりたい。 例えば下くらいのオブジェクトの配列で。

var hogeobj= {
    date: "2016-04-01",
    group: {
        classname: "都道府県",
        values: [
            { name1: "青森", name2: "りんご", value: 6 },
            { name1: "秋田", name2: "美人", value: 10 },
            { name1: "秋田", name2: "きりたんぽ", value: 2 },
            { name1: "宮城", name2: "笹かま", value: 3 },
            { name1: "青森", name2: "りんご", value: 8 },
            { name1: "青森", name2: "美人", value: 4 },
            { name1: "秋田", name2: "笹かま", value: 15 },
            { name1: "宮城", name2: "桜", value: 1 },
        ]
    }
}

上の感じのデータセットで、例えば、各オブジェクト内のname1のグループ毎にvalueを合計したいとか。下の感じ。

var groupCountObj = d3.nest()
                            .key(function (d) { return d.name1; })
                            .rollup(function (v) { return d3.sum(v, function (d) { return +d.value; }); })
                            .map(hogeobj.group.values);
console.log(groupCountObj);
->Object {青森: 18, 秋田: 27, 宮城: 4}

集計はされるんだけど、Objectで結果が戻ってくるので割と扱いにくい。なので、配列にkey-value的な感じでmapする。

var tmpob = $.map(groupCountObj, function (value, index) {
    return { label: index, value: value }
});
console.log(tmpob);
->[Object, Object, Object]
0:Object
label:"青森"
value:18
__proto__:Object

1:Object
label:"秋田"
value:27
__proto__:Object

2:Object
label:"宮城"
value:4
__proto__:Object

length:3
__proto__:Array[0]

これでD3使っての円グラフとかに使いやすくなる。

久しぶりにD3使ったけど、すごい忘れてる。。。えらいこっちゃ。

問合せ