tekitoumemo’s diary

思ったことを書くだけ。長文版Twitter

【C#..Net】.ctor()って何?


VB.NETで起きた謎の.ctor。

.Net Frameworkのバージョンアップをやった時に一瞬で落ちたのでイベントビューアーでログ解析したら以下のエラーが発生しました。

アプリケーション:○○.exe
フレームワークのバージョン:v4.0.30319
説明: ハンドルされない例外のため、プロセスが中止されました。
例外情報:System.IO.FileLoadException
場所 ○○.Main..ctor()

ぼく「FileLoadException」あぁ、ライブラリのFrameworkバージョンが一致しないとかそんなエラーだろうなぁ、どこで落ちてるのかな?

・・・

.NET「.ctor()」
ぼく「誰だよ!」

って感じでちょっと困りました。

これはコンストラクタで.NETの中間言語が出力されてるらしいです。

VB嫌いだなぁと思ったこの頃。

githubを導入して一年たったので振り返る


一年前に全サービスをgitに移行しました。

そろそろ運用も落ち着き、通常通り開発が出来るようになったので振り返ってみようと思います。

はじめに、以前はSubversionを使ってましたが、なぜgithubに移行した方が良いと思ったのか以下に書きます。

リリース時のバグが多い

リリースの曜日は決まっているのですが、リリース日にコミットする人がいたり、いつ誰がコミットしたか管理出来ていなかったのでリリースに「なんだこれは!」ってのが多かった気がします。プルリクエストを導入して、ヤバいソースコードがマージされることは少なくなりました。

ブランチの管理が出来ていない

Subversionのときは一回マージ漏れしたら差分が出過ぎて手に負えません。同期が取りづらい印象。gitはコミット単位の同期になるので確実に同じ内容のブランチが作れるのでミスが少ないですし安心します。

コミットの粒度がめちゃくちゃ

誰も確認しない状態だったので、大量の修正も簡単にコミットされてました。プルリクを導入したことによって人に見られるという意識が増えたのでしょうか?なにもせずにコミットの粒度が整ってきたように感じます。僕のブログも以下の記事のときの月間PV数が50も満たない感じだったけど1500程度の今とは比べ物にならないぐらい文章が丁寧になりました(これでもね。)
tekitoumemo.hatenablog.com
ほんとに何言ってるのかわからん。。

社内の技術力の不安

うちの会社はエリート揃いなので技術力はないことはないのですが、ちょっと新しい技術に興味ある人は少ないかなーと思いました、仕事が忙しいからそれどころじゃないってのもあるんですが。新しい仕組みの理解や運用フローの再認知を含め、今のチームには必ず必要だと感じていました。

協力会社のソース共有が出来ない

厳密にはできるのですが、圧倒的にラクになりました。アカウント作って招待すれば一瞬で使えるようになるし、協力会社さんは社員と比べてどうしてもコミュニケーションが少なくなってしまうのでプルリクはかなり役に立っています。

これらの課題を解決するには、導入だけでは改善出来ないので運用フローも一緒に考えました。ここでは代表的なフローであるGitFlowとGithubFlowを説明します。

GithubFlowとは?

f:id:tekitoumemo:20180226224504j:plain
WEBサービスでは一般的なフローかなと思います。masterブランチからfeatureブランチを作って都度マージしていくやり方です。こちらのフローは常にmasterブランチが改善され、リリースのサイクルが短くなるのでアジャイル開発に向いているフローです。運用方法などシンプルでわかりやすくリリースサイクルが短いという利点はありますが、自動テスト、自動デプロイの環境が整ってなかったり、WEBサービスを作っている人はエンジニアだけではないので、マーケティング、サポートなど様々な職種の人と作り上げていく上では難しさを感じます。スタートアップであれば非常に良いフローだと思います。

GitFlowとは?

f:id:tekitoumemo:20180226230503p:plain
定期的に更新するブランチはdevelop、長期に渡る開発はfeature、緊急で対応が必要な場合はhotfix、リリースはrelease、本番と同様の状態のmasterの5つで構成されている運用方法です。一つ一つのブランチに意味があり、フローを理解するにはちょっと難しいフローかなぁと思います。良い面は役割がきっちりしているので、手戻りが少なく、堅実に運用していることです。人数が多かったり、WEBサービスを取り巻く業種の人がかかわっていたりする際には非常に良いフローとなっています。定期的なリリースを止めずに大きな開発を平行していけるのでサービスを大きく変化させていくことが出来ます。うちの会社は以前も同じようなフローを使っていて、下手に運用を変えるとトラブルにつながるため、こちらの方法を採用しました。運用を変えるというのは、個人にとっては大したことなくてもチームとしては大きな変化をもたらすので注意が必要です。ちなみにreleaseは必要性を感じなかったですし、リリースの際にいちいちブランチを切ってもしょうがないと思ったので運用から外しました。実際にうまく運用出来ているので、正解でした。

次にSubVersionで運用していた仕組みをGitに移行出来るか考えました。
独自のライブラリ群、Areasをexternals (外部参照) を多用していたため、Gitでの代替えを考えました。ちなみにAreasとは以下のようなものです、ここでは説明しません。
チュートリアル: 区分による ASP.NET MVC アプリケーションの編成
Gitでは外部参照がなく、困りましたがSubmoduleを採用し独自のライブラリ群、Areasをリポジトリに分ける作業をしました。
qiita.com
分け方は

大きく分けて、この三つで構成します。こちらで問題なかったのですが、Submoduleが癖がありSVNシンボリックリンクでSubmoduleは外部リポジトリのコミット番号参照だったので運用方法が理解されるまで時間がかかりました。ここは正解か不正解か難しいところですが、コミット毎に参照できるのでサービスごとの仕様でリリースできるのでなかなか良かったと思ってます。

次にプロジェクトファイルとソリューションファイルのパスを変更しました。なかなかしっちゃかめっちゃかになってるので、ちゃんと修正するのが大変でした。絶対パスで指定されているところは相対パスに、変更された場所はすべて修正しました。いい機会なので不要なものも削除し非常にすっきりしました。

最後にクライアントツールの選定です。
SVNで使っていたSVNのクライアントツールがありましたので、そちらのGit版を採用しました。
TortoiseGit – Windows Shell Interface to Git
極力、今まで使い慣れていたツールなどの運用を変えずに移行することを心掛けていたのでこちらはすんなりいきました。Gitはあくまでもソース管理ツールなので、使えれば良いぐらいなのであまり深堀はしていません。

その他、Jenkinsの設定や移行するプロジェクトのスケジュール管理など移行に伴う作業を一通りやりました。ソース管理の移行は単純にコピーなので簡単なのですが、チームに認知したりスムーズに運用する上でのフローを考えたりなどやることは多かったと思いますが、良い経験になりました。移行してみて、hotfixなどほとんど作成したことないですし運用を含めリリースの精度が上がったので振り返ってみて確実に良かったと思いました。新しいツールを導入することは良いですが、運用を含めチームがより良い方向に進むためにちゃんと考えて運用出来ればよいですね。

【ASP.NET API、C#】パターンによって様々なレスポンスを返す方法


小ネタ。オブジェクト志向を理解していれば当たり前の話と思われるかもしれませんが、割と使われてない感じがしたので書きます。

パターンによる様々なレスポンスとは?

RESTful APIでは、リクエストを受けてjsonで返すことが多いと思います。こんな感じで。

{
  "hoge1": "hoge1",
  "hoge2": "hoge2"
  }
}

特定のパターンだけ値を追加したい、または減らしたいときにプロパティを追加するとこんな感じになってしまいます。

{
  "hoge1": "hoge1",
  "hoge2": "", // ここだけ空だよ
  "hoge3": "hoge3" // 追加されてるよ!
  }
}

このままだとパターンによって増えたり減ったりしてカオスなAPIになるので、様々なパターンによってレスポンスをコントロールしたいときがあります。そもそもこんな感じになる前にAPI設計をちゃんとしろ思うかもしれませんが、生きてるサービスにそもそも論は通用しないのでどのように改善出来るかが、僕らの腕にかかってます。

本題に戻ります。

パターンによって複数のレスポンスを返す方法

ざっと説明すると、戻り値のクラスを継承するだけです。

    public class Hoge
    {
        public string Hoge1 { get; set; }
        public string Hoge2 { get; set; }

    }
    public class Hogeα : Hoge
    {
        public string Hogeα1 { get; set; }
        public string Hogeα2 { get; set; }
    }

        public Hoge Get()
        {
            var model = new Hoge();
            model.Hoge1 = "hoge1";
            model.Hoge2 = "hoge2";
            return model;
        }

        // GET api/values/5
        public Hoge Get(int id)
        {
            var model = new Hogeα();
            model.Hoge1 = "hoge1";
            model.Hoge2 = "hoge2";
            model.Hogeα1 = "newhoge1";
            model.Hogeα2 = "newhoge2";
            return model;
        }

派生クラスに拡張したいプロパティを指定して、基底クラスは必要最低限のプロパティで収めます。ここで鍵となるのは、戻り値に基底クラスを指定することです。基底クラスのHogeを指定すると派生クラスで拡張したいプロパティも含めてレスポンスが戻ってきます。

継承の包含関係は以下の図で表している通り、基底クラスに内包されたかたちで派生クラスが存在します。

なので、戻り値に基底クラスを指定しても派生クラスのプロパティを含めたレスポンスが戻ってくるという仕組みです(たぶん)

これを使えば、用途ごとのモデルを作って可読性が上がるので良いと思いました。多様しすぎて何のためのAPIかわからなくなってもいけないので、ほどほどにしましょう。

WindowsUpdateをしたらSynapics ClickPadが動かなくなった(2018/02/25時点)。

f:id:tekitoumemo:20180225143526p:plain
先週のアップデートでSynapics ClickPadが動かなくなりました。最近Creators Updateやらアップデートによる不具合が多いです、勘弁してください。まずは端末情報やらは以下の通りです。
tekitoumemo.hatenablog.com

  • 端末情報:CloudBook(Chrome Bookではありません。)
  • プロセッサ:Intel(R) Celeton(R) CPU N3050 @ 1.60GHz
  • メモリ:2.00GB

kakaku.com

僕は2万円で購入したのですが、値段なりのポンコツスペックです。
使用用途はメール、ブログ、マークアップコーティング程度でそれ以外は全く使えません。以前はUbuntuを入れていて、.Net CoreのAngular開発がしたかったのでOSを入れ替えました。しかし、Visual Studioが重くて全く使い物にならなかったため、割り切って簡単な使用用途に限定しています。アップデートが使えなさすぎるので近々Ubuntuに入れ替えます(めんどくさい)。UbuntuもClassisでなければまともに使えないので、軽量なディストリビューションを選べばそれなりに使えるはずです。

本題に戻りますが、今月のWindowsUpdateによってSynapics ClickPadが動かなくなりました。おそらく原因はこいつです。

Windows 10 Version 1703 for x64-Based Systems 用更新プログラム (KB4023057)

マウスのプロパティでこのタブが表示されなくなり、デバイスマネージャーでもデフォルトのドライバが入っていることはわかります。
f:id:tekitoumemo:20180225150730p:plain

解決方法

使えていたころのWindowsUpdateに戻すだけです。
https://cs.myjcom.jp/servlet/servlet.FileDownload?file=01528000003lnUx

ここらへんは原因探っても時間の無駄だと思うので、割り切って戻しちゃってもいいかなと思います。ノートパットでタッチパットが使えないのは致命的なので本当にやめてほしいと思いました。

【ASP.NET MVC.C#】ViewEnginesでランディングページを効率良く量産する方法

ランディングページをサイトに組み込むとき、よくデザイナーさんにこんな質問されます。

「どこのファイルを編集すれば良いですか?」
「どんなurlになりますか?」
「ページ作ったら教えてください」

ちゃんと説明して作ってもらえるようにした方が良いのでしょうが、自分で動いてもらってプルリクエストを受けるだけの流れを作れると効率よくなると思います。なので、今回はViewEnginesを使ってデザイナさんでも効率よくランディングページを運用できるものを紹介します。

ランディングページとは?

1つの商品、サービスを売るための1枚の長いWEBページのことを指しており、広告などの流入元となるページとしてよく使われます。季節的な商品や業種などのターゲットに適したマーケティングができるので非常に重要なものとなります。

どうやって効率を上げていくか?

今回のケースでは、エンジニアとデザイナのやり取りが頻繁に起こるので、そのやり取りを極限まで上げる方法です。具体的には以下の通りです。

宣伝したい商品:セーター

デザイナ「うちの商品は軽くて着心地が良いセーターを売っているので、「lp/lightsweater」というURLでLPを作ろう!」
デザイナ「…(モクモクこなす)」
エンジニア「…(自分の業務をモクモクこなす)」
デザイナ「出来ましたー!」
エンジニア「おぉ!(なにもしてない)」

これが理想的なフローです。

では、コードの解説に移ります。全体的なコードは以下です。

        public ActionResult lp(string name)
        {
            ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, name, null);
            if (result.View != null) 
            {
                return View(string.Format("~/Views/lp/{0}.cshtml", id));
            }
            return HttpNotFound();
        }

ViewEnginesクラスのEnginesプロパティのFindViewメソッドを使ってパラメータで指定された名前と一致したViewを取得します。

      ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, name, null);

Viewプロパティに検索結果が返ってくるので存在したらViewの場所を指定してreturnすると指定したビューが表示されます。それ以外はHttpNotFoundを指定すると404のページが表示されます(エラー処理してね)

if (result.View != null) 
{
     return View(string.Format("~/Views/lp/{0}.cshtml", id));
 }

セキュリティの懸念とかちゃんと考えてない実装方法ですが、ランディングページなど置く場所をちゃんと運用すればかなり実用的な方法になるかと思いますし、実際に業務ではこちらで運用しています。

【ASP.NET MVC, C#】部分ビューでフォームとAjaxを扱う

f:id:tekitoumemo:20180220214244p:plain
MVCで開発してて便利な部分ビューがありますが、フォームとAjaxを扱うことでめんどくさいエラー処理やモーダルで扱う入力処理などめちゃくちゃ楽になったので書きます。サンプルは以下に貼っておきます、参考程度に。
github.com

「何言ってるかわからない」

と思う人も多いかと思いますので、事例を使ってどういうときに便利なのか説明します。
以下のようなモーダルで表示するログイン画面がありますが、モーダル以外にも独立したログイン画面があって複数書くのがめんどくさかったり、冗長になってしまいます。さらにWEBサービスで導線を考えるときにコンバージョンが会員登録、問い合わせになったりするのでいろんなところから多様して使うことも非常に多いです。
f:id:tekitoumemo:20180220215056p:plain
しかもこのような会員登録処理や問い合わせ画面は、エラー処理などアノテーションで対応するのでフォームを使っていることが多く、がちがちに処理が固定されているので汎用的に使えなく結局は別で作成するという羽目になったりします(実際にこういう仕事をしました>馬鹿だった)そこで、部分ビューとして固定されているフォームを使ってアノテーションで判定されたエラーをAjaxで処理出来るとそのまま扱えて楽ちんだねって手法を見つけたのでどのようにやったか説明します。

まずはGetアクションを用意します。ChildActionOnlyは子ビューでしかつかえないよ!ってやつなので僕は必ずつけます。モデルは必要に応じてアノテーションを付けときます。アノテーションのメッセージはデフォルトだと使い物にならないのでErrorMessageを使ってそれとなく通じるメッセージに変更しておきましょう。今回は指定しません。

        [ChildActionOnly]
        public ActionResult Child()
        {
            var model = new ChildModel();
            return View(model);
        }
...
    // 子アクションに使うモデル
    public class ChildModel
    {
        [Required]
        [StringLength(10)]
        public string Name { get; set; }
        [Required]
        [StringLength(255)]
        [EmailAddress]
        public string Email { get; set; }
    }

ビューを用意します。簡単に名前とメールアドレスのテキストとsubmitボタンを用意します。Layout はヘッダー、フッターのデザインマスタを指定しますが部分ビューなので空にしておきます。

@{
    ViewBag.Title = "Child Page";
    // 子ビューだからいらない!
    Layout = "";
}

<div id="MailForm">
    <div class="col-md-4">
        <h2>Child Action</h2>
        @using (Html.BeginForm("Child", "Home", FormMethod.Post))
        {
            @Html.ValidationSummary("")
            @Html.AntiForgeryToken()
            @Html.TextBoxFor(model => model.Name)
            @Html.TextBoxFor(model => model.Email)
            <input id="send" type="submit" value="送信" />
        }
    </div>
</div>

次にPostメソッドを用意します。フォーム通信には必ずValidateAntiForgeryTokenを付けましょう。ValidateAntiForgeryTokenはフォームで生成したトークン(Html.AntiForgeryToken())をチェックし、違っていたら処理をはじいてくれます、超便利。ModelState.IsValidはモデルの状態を表していて、アノテーションでチェックに引っかかった場合にFalseとなります。なにも処理せずにViewを返すと@Html.ValidationSummary("")でリストタグとしてエラーを勝手に表示してくれます。

        [HttpPost]
        [ValidateAntiForgeryToken()]
        public ActionResult Child(ChildModel model)
        {
            if (ModelState.IsValid)
            {
                // 成功
                var result = new ContentResult() { Content = "Success" };
                return result;
            }
            
            return View(model);
        }

次に通信するためのAjaxを用意します。Ajaxはヘルパーでも使えるのですが、あまり使いやすくない(知識ないだけ)のでゴリゴリに書きました。ここでのコツはevent.preventDefault();で送信処理をキャンセルしてください、2回Postされちゃうので。$('form').serialize();でフォームの値が取得できるのでそれをAjaxにセットします。functionでエラーの場合はHTMLがごっそり返ってくるのでJqueryのDom操作を使ってごっそり入れ替えます。そうすると、エラーの場合はフォームで生成されたエラーエッセージが@Html.ValidationSummary("")を配置した場所にリストタグとして出力され、成功した場合は他の処理をすればよいだけです。

$(document).on('click', '#send', function (event) {
        // HTMLでの送信をキャンセル
        event.preventDefault();
        // formデータを取得&送信
        var formData = $('form').serialize();
        $.ajax({
            async: false,
            url: ' /Home/Child',
            type: 'POST',
            data: formData,
            timeout: 10000,
            dataType: 'text',
            success: function (data) {
                if (data == "Success") {
                    location.href = "/";
                } else {
                    //エラーの場合、MailFormのDomを差し替える
                    $("#MailForm").html(data);
                }

            }
        });
    });

これらを駆使すれば、フォームを使っていた処理をそのまま使えてAjax通信でリロードしなくてもエラーメッセージが使えるので非常に便利です。子ビューは覚えるといっぱい使いたくなりますが、いろんなところで扱うビューだけにしたほうがよいです。僕も結構好きでいろいろ使いましたがわかりにくくなっちゃったりしたので微妙でした、Angularなどのコンポーネント志向にどっぷりつかる感じと似てるかも。

割とメジャーな技術なので、知ってる方も多いと思いますが、この記事でちゃんとしたところ(無駄に子ビューを作らない)で汎用的に部分ビューを扱えればと良いなと思います。

【C#、ASP.NET MVC】独自クラスのプロパティをNULL以外の値で初期化する


技術ネタというよりはテクニックネタ。

独自クラスのプロパティを初期化をするときにStringやらNullableやらリストやらをdefault(T)で初期化するとNULLになってしまいます。

default(T)とは


単純に初期化をするだけなのですが、ジェネリックで指定出来るので0やらNULLやらと型によって値を変えなくて良いやつです。

規定値がNULLだとビミョーなのか

規定値がNULLなのでその通りに扱えば良いのですが、現場では意外に規定値以外の初期化は必要になったりします。例えば、entityframeworkやDapper※はテーブルと同じ形のモデルクラスを持っていて、とあるテーブルのカラムがNULL許容していたらNullable<T>となってしまいます。実際にテーブルのモデルをビューモデルで扱ったりすると、0と表示させたい場合もあるのでNULLは扱いにくかったりします。
※Dapperは独自でテーブルのモデルクラスを作るので、全く同じとは限りません。entityframeworkもモデルクラスをカスタム出来るので、全く同じとは限りませんが、自動で作るという点で考えると全く同じという認識でも問題ないかもしれません。

今回はクラスのプロパティを一括してNULL以外の規定値に変えるプログラムを作ったので、解説します。今回のプログラムはNULLは0やnewなどの初期化をし、すでに初期化されているプロパティは変換しません。

なぜこれを作ったのかというと、それぞれのビジネスロジックやモデル内で以下のような初期化コードを見つけたりします。

model.hoge1 = 0;
model.hoge2 = ””;

べつにこれでもいいっちゃいいのですが、無法地帯になると肥大化しまくります。ここら辺の問題はモデルに紐づくのでモデルのベースクラスに定義します。

     /// <summary>
    /// Baseクラス
    /// </summary>
    public class BaseClass
    {
        /// <summary>
        /// 特定の規定値で初期化を行う
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public void InitializingAbstractionClass<T>()
        {
            var properties = typeof(T).GetProperties();
            foreach (var property in properties)
            {
                var value = typeof(T).GetProperty(property.Name).GetValue(this, null);

                // 値がない場合に初期化
                if (value == null)
                {
                    var returnType = property.GetMethod.ReturnType;

                    // string型は既定値を空白にする
                    if (returnType == typeof(string))
                        value = string.Empty;
                    // Nullable型は既定値を0にする
                    else if (returnType == typeof(Nullable<byte>))
                        value = (byte)0;
                        ・・・
                    // その他、自前クラス等を初期化する
                    else
                    {
                        var currentType = Type.GetType(typeof(T).GetProperty(property.Name).PropertyType.FullName);
                        value = Activator.CreateInstance(currentType, null);
                    }
                }
                property.SetValue(this, value);
            }
        }

まず、ジェネリックで渡されたTypeからプロパティ一覧を取得します。

typeof(T).GetProperties();

プロパティのGetMethod.ReturnTypeからプロパティのTypeを取得し、Typeの初期化を行います。

if (returnType == typeof(string))
    value = string.Empty;
// Nullable型は既定値を0にする
else if (returnType == typeof(Nullable<byte>))
    value = (byte)0;

独自クラスなどnewしなければいけないプロパティについては、インスタンスを作成します。

Activator.CreateInstance(currentType, null);

最後にSetValueするだけです。

SetValue(this, value);

このクラスをビューモデルなどに継承させ、初期化の度に呼び出したり、コンストラクタに利用したりしてもよいと思います。以下のような形で呼び出します。

model.InitializingAbstractionClass<Hoge>();

以下のクラスがすべて初期化されました。

/// <summary>
/// Hogeクラス
/// </summary>
public class Hoge : BaseClass
{
    public Nullable<byte> Hoge1 { get; set; }
    public Nullable<short> Hoge2 { get; set; }
    public Nullable<ushort> Hoge3 { get; set; }
    public Nullable<int> Hoge4 { get; set; }
    public Nullable<uint> Hoge5 { get; set; }
    public Nullable<long> Hoge6 { get; set; }
    public Nullable<ulong> Hoge7 { get; set; }
    public Nullable<float> Hoge8 { get; set; }
    public Nullable<double> Hoge9 { get; set; }
    public Nullable<decimal> Hoge10 { get; set; }
    public string Hoge11 { get; set; }
    public ChildHoge Hoge12 { get; set; }
}

// 結果
Hoge1 = 0
Hoge2 = 0
Hoge3 = 0
Hoge4 = 0
Hoge5 = 0
Hoge6 = 0
Hoge7 = 0
Hoge8 = 0
Hoge9 = 0
Hoge10 = 0
Hoge11 = ""
Hoge12 = ChildHoge()

これで無駄な初期化処理がなくなりコードがすっきりするので、ジェネリックおすすめです。正直、ざっと作ったので実用的かといわれると微妙ですが、参考程度にこんなものもあるんだぐらいの認識でよいかと思います。

PS:動的Typeをキャストする方法ってないの?
Convert.ChangeType メソッド (Object, Type) (System)
これだとキャストできなかったりするので、簡単な方法でやり方を知っている人がいたら教えてください。