Try .NET Core

.NET Coreを動かした、試した記録を書き残します。

音楽サーバ"Mopidy"のフロントエンドを作る:01 AspCore+TSの環境作り

ここを最後に更新してから、はや2年半が過ぎました。
.Net Coreはそろそろ3.0が出るかというこの頃。初期の粗削りさは影を潜め、環境もドキュメントもリッチになり、とても便利になりました。

個人的に少し時間とモチベーションが出来たので、現在の.Net Core環境をベースに、アプリ作りの過程を記事に起こしてみようと思います。
なお、アプリ自体は既に出来上がっており、こちらに公開しております。

Mopidyとは?

Mopidyとは、Linux上で動く音楽サーバです。
純粋なサーバ実装で、UIはありません。フロントエンドは各自好きなExtensionを入れてね、というスタンスです。
フロントエンドとしては、IrisMopedなどが有名です。

既にIrisみたいなオシャレで出来の良い鉄板プロダクトがあるのに、なぜ新しいものを作ろうと思ったか、というと。

大量に曲を登録したときに、お目当ての曲を探し出すのにひと苦労するんですね。
アルバムやアーティストの名前や綴りを覚えてないと、検索もままならない。

というか、検索するのにキーワードを入力しなきゃいけないのが嫌!
ぽちぽちクリックしたらお目当てが出てくるようにならないの...?

これはMopidy本体の検索APIが貧弱なためで、どのフロントエンドを選んでも同じ問題に行き当たります。
ちょうど時間も出来たことだし、いっちょう作ってみるか、となりました。

何が出来たらOKとする?

まず、作るにあたって要件の整理です。クリアしたい内容としては...

  • Mopidy上のアルバムや曲が探しやすいこと。一番の動機です。
  • Raspberry-Pi上で動作すること。我が家のMopidyサーバがラズパイにあるので。
  • お客さんからAdmin-LTEというフレームワークの話を聞いたので、実際に試す。
  • TSはとっても楽しい!ので、ベース環境はAspCore+TypeScript+Vueにする。

こんなものです。

ベース環境づくり

このあたりのコミットで、おおよその土台を作っています。

Visual Studio 2017でAspCore2.2のプロジェクトを作り、AspCoreのコードを"aspCore"フォルダに全部押し込みました。
今回はSPAのつもりなので、AspCoreのViewはまるっと削除。
HomeController.Indexメソッドはdist/index.dev.htmlを返すようにしてあります。

TSのテスト環境としてmocha, chaiを導入し、TS側モデルで読み込んだlodashが動作してるよね、というところを確認。
f:id:try_dot_net_core:20190731214517j:plain
eslintが大量のダメ出しをしてくれていますが、ひとまずスルーです。

Visual Studioでステップデバッグするために

Visual Studio 2017では、TSをwebpack出力した状態では、エディタにブレイクポイントを置いてもブレイクしてくれません。
今までにも度々試行錯誤を重ねたのですが、残念ながら方法が見つかっていません。

そこで、大変回りくどい方法で「外部ライブラリはwebpackを使用」しつつ「TSはtscが出力したものを使用」することで回避しています。

TSは、AMD形式で単一ファイルとして書き出します。
tsconfig.dev.json:

  -- 一部抜粋 --
"compilerOptions": {
    // モジュール単位の出力形式指定: commonjs, amd, system, umd, es2015
    "module": "amd",

    // 単一ファイル出力時のパスとファイル名
    "outFile": "dist/js/tsout.js"
}

CSSと外部ライブラリは、一旦一つのjsファイルで全て取得し、windowオブジェクトに書き出します。
js/libraries.js:

import '../css/site.css';
import * as es6Promise from 'es6-promise';
import * as _ from 'lodash';
import Axios from 'axios';
import * as Enumerable from 'linq';

window.__globals = {
   'es6-promise': es6Promise,
   'lodash': _,
   'axios': Axios,
   'linq': Enumerable
};

windowオブジェクトに書き出された各ライブラリを、今度はAMDモジュール形式にラップします。
最後に、TSの起動クラスを読み込んでTSロジックを開始しています。
dist/boot.js:

// ダミーのCSS
define('../css/site.css', ["exports"], function (exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.default = {};
});

// es6-promise
define('es6-promise', ["exports"], function (exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.default = window.__globals['es6-promise'];
});

// lodash
define('lodash', ["exports"], function (exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var instance = window.__globals['lodash'];
    for (var key in instance) {
        var val = instance[key];
        exports[key] = val;
    }
});

// Axios
define('axios', ["exports"], function (exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.default = window.__globals['axios'];
});

// linq.js
define('linq', ["exports"], function (exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.default = window.__globals['linq'];
});

// AMD出力されたTSロジックを起動する。
var app = require(['Main']);

htmlでは、AMDモジュールを扱うためにRequire.jsを組み込んだ上で、順次取得・処理していきます。
dist/index.dev.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0" />
    <title></title>
</head>
<body>
    <script src="js/require.js"></script> <!-- Require.js -->
    <script src="js/libonly.js"></script> <!--  webpack出力 -->
    <script src="js/tsout.js"></script>  <!-- tsc出力 -->
    <script src="js/boot.js"></script>  <!-- 前述のAMDラップ/起動スクリプト -->
</body>
</html>

こうすると、Visual Studioのエディタ上で設定したブレイクポイントで、TSのロジックが止まってくれます。
f:id:try_dot_net_core:20190731222208j:plain
生成済みの song1 オブジェクトの中身に何が入っているかも、参照出来てますね!

なお、本番ビルドの場合は、TSを含めた全てをwebpackするだけにします。
tsconfig.commonjs.json:

  -- 一部抜粋 --
"compilerOptions": {
    // モジュール単位の出力形式指定: commonjs, amd, system, umd, es2015
    "module": "commonjs"

    // 単一ファイル出力時のパスとファイル名
    //"outFile": "dist/js/tsout.js" //無効化
}

htmlは、バンドルされた1本のjsを読み込みのみです。
dist/index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0" />
    <title></title>
</head>
<body>
    <script src="js/bundle.js"></script>
</body>
</html>

なお、この方法は決して推奨されるものではありません。
今のところ、他に良い方法が見つかっていないだけ、なのです。
良い方法があれば、どなたかぜひご教示くださいませ...。

Xamarin/.NET Core対応SMBアクセスライブラリを公開しました

Xamarin, .NET Coreで動作するSMB/CIFSライブラリ SharpCifs.Std を公開しました。
Android / iOS / Linux から、Windows共有フォルダ へアクセスするためのものです。

概要

Windows Phone 8用のSMBライブラリSharpCifsを、.NET Standardで動くように少々手を加えたものです。
SharpCifsJCIFSの移植版なので、移植の移植といったところ。

ソースがこちらNuGetパッケージはこちらです。

公開にあたっては、SharpCifs作者のJ. Arturoさんに快諾頂き、嬉しい限りです!

インストール

パッケージをnuget.orgに公開しているので、Visual StudioNuGetパッケージマネージャからインストールできます。
プレリリース版につき、「プレリリースを含める」チェックをONにしてください。
f:id:try_dot_net_core:20161228121610j:plain

使い方

JCIFSそのままです。

フォルダ上のアイテム取得:

var folder = new SmbFile("smb://UserName:Password@ServerName/ShareName/Folder/")); //<-need last'/' in directory
var epocDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
foreach (var item in folder.ListFiles())
{
    var lastModDate = baseDate.AddMilliseconds(item.LastModified()).ToLocalTime();
    Console.WriteLine($"Name: {item.GetName()}, isDir?: {item.IsDirectory()}, Date: {lastModDate.ToString("yyyy-MM-dd HH:mm:ss")}"); 
}

ファイル読み込み:

var file = new SmbFile("smb://UserName:Password@ServerName/ShareName/Folder/FileName.txt"));
var readStream = file.GetInputStream();
var buffer = new byte[1024*8];
var memStream = new MemoryStream();
int size;
while ((size = readStream.Read(buffer, 0, buffer.Length)) > 0)
    memStream.Write(buffer, 0, size);
    
Console.WriteLine(Encoding.UTF8.GetString(memStream.ToArray()));

ファイル書き込み:

var file = new SmbFile("smb://UserName:Password@ServerName/ShareName/Folder/NewFileName.txt"));
file.CreateNewFile();
var writeStream = file.GetOutputStream();
writeStream.Write(Encoding.UTF8.GetBytes("Hello!"));

参考:JCIFS API
https://jcifs.samba.org/src/docs/api/

こんな感じに動きます

試しに、共有フォルダ内のリストを取ってきます。
アクセス先はこちら。
f:id:try_dot_net_core:20161228122856j:plain

おおむねこんなコードを書きます。
日時はlongで取れてきます。UnixTimeのMillisec単位になってます。

var epocDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var folder = new SmbFile("smb://UserName:Password@ServerName/ShareName/Musics/Mp3/Savage Garden/AFFIRMATION/");
var list = new List<string>();
foreach (var item in folder.ListFiles())
{
    var lastModDate = epocDate.AddMilliseconds(item.LastModified()).ToLocalTime();
    list.Add($"{item.GetName()}: {(item.IsDirectory() ? "folder" : "file")}, {lastModDate.ToString("yyyy-MM-dd HH:mm:ss")}");
}
list.Sort();
var result = string.Join("\r\n", list);

Xamarin.Android f:id:try_dot_net_core:20161228140214j:plain

Xamarin.iOS f:id:try_dot_net_core:20161228124235j:plain

.NET Core f:id:try_dot_net_core:20161228124249j:plain

各プラットフォームとも、ファイル、ディレクトリどちらも取れてます!
文字化け無く、日時も正確ですね。

参考リンク

GitHub - zinkpad/SharpCifs: SharpCifs is a port of JCIFS to C#
https://github.com/zinkpad/SharpCifs

JCIFS - The Java CIFS Client Library
https://jcifs.samba.org/

GitHub - ume05rw/SharpCifs.Std: [pre-release] Port of SharpCifs to .NET Standard
https://github.com/ume05rw/SharpCifs

NuGet Gallery - SharpCifs.Std 0.1.3-beta1
https://www.nuget.org/packages/SharpCifs.Std/

.NET技術の対象、関係性を整理する

Microsoftはここ数年、.NET技術の対象プラットフォームを意欲的に拡大しています。
XamarinでAndroid, iOSなどのモバイルOSを、.NET CoreでLinuxサーバプラットフォームを取り込み、なりふり構わず邁進しています。

また、拡大した対象の開発技術のほとんどを無償で使うことができ、従来からの C#er / VBer には嬉しい限りです。

しかしながら、変革速度があまりに速いため、どの技術が何に対応するのか、いまひとつわかりにくい状態になっています。

そこで今回は、主要フォーマットごとの対象や特徴、関係性を整理してみます。

.NET Framework

従来のいわゆる「ドットネット」といえば、こちら。
f:id:try_dot_net_core:20161219222233j:plain

対象プラットフォームは、Windowsだけです。
クラスライブラリとして参照可能なフォーマットは、.NET Framework / .NET Standard / Portable Class Library(PCL) の3種類です。
SI業界にはお馴染みですね。

歴史が長いため実績も情報も多く、サードパーティライブラリが充実しています。

以下のフォーマットは、この.NET Frameworkのサブセットを基本としています。


.NET Core

本ブログのテーマ、.NET Coreです。
f:id:try_dot_net_core:20161219222106j:plain

Windows / Linux / macOS の3つのプラットフォームで動作します。
クラスライブラリとして参照可能なフォーマットは、.NET Core / .NET Standard の2種類だけです。

.NET Frameworkのうち主要部分の多くが移植されており、Windowsプログラマにとっては敷居が低くなっています。
Windows.FormsWPFなどのUI関連機能はオミットされており、Webアプリケーション / API などのサーバサイドがターゲットになりそうです。

今回紹介する中では最後発のフォーマットになるため、まだ日本語情報が少なく、外部ライブラリの対応も現在進行中といったところです。


Xamarin.Forms

Microsoftが先だって買収したXamarinの中でも、最も意欲的なフォーマットです。
f:id:try_dot_net_core:20161219222140j:plain

Android / iOS / Windows Phone / Windowsストアアプリ など、広いプラットフォームを対象としています。
クラスライブラリとして参照可能なフォーマットは、主にPortable Class Library(PCL) の1つだけです。
しかし、中枢となるXamarin.Formsプロジェクトを.NET Stadardフォーマットに変換することで、.NET Standardフォーマットのライブラリも参照可能になっています。

対象が広い上にUIを制御下に置くため、機能的には各プラットフォームの最小公倍数的な実装になっています。

厳密にはXamarin.Formsが直接プラットフォームの上で動くわけではなく、各プラットフォーム向けのサブプロジェクトを経由して実行されます。
実質的にPortable Class Library(PCL) (もしくは.NET Standard) として動作しており、.NET Frameworkサブセットとしては最小限の機能に制限され、開発難易度が高くなっています。

参考:
.NET Standard Library with Xamarin Forms - Xamarin Help
GitHub - adamped/XamarinForms.NetStandard: Sample application showing .NET Standard with Xamarin Forms


Xamarin.Android

Xamarinの中で、Androidを対象としたフォーマットです。
f:id:try_dot_net_core:20161219222205j:plain

対象プラットフォームはAndroidのみ。
しかし参照可能なクラスライブラリフォーマットは多く、.NET Standard / Portable Class Library(PCL) / Xamarin.Android / .NET Framework の4種類です。
ただし.NET Frameworkの全機能が使えるわけではなく、バイナリ参照の際はビルドが通っても実行時に落ちることがあり、注意が必要です。

.NET Frameworkサブセットとしては、Portable Class Library(PCL)より広い範囲の機能が用意されています。
中身はAndroid SDKのラッパーなので、C# で実装するというよりは、Android-JavaC# に翻訳するように書き進めることになります。

Xamarin.iOS

Xamarinの中で、iOSを対象としたフォーマットです。
f:id:try_dot_net_core:20161219222221j:plain

当然ながら対象プラットフォームはiOSだけです。
Xamarin.Androidと同様に多くのクラスライブラリフォーマットが参照可能で、.NET Standard / Portable Class Library(PCL) / Xamarin.iOS / .NET Framework の4種類です。
注意点も同じく、.NET Frameworkのサブセットであることを意識する必要があります。

こちらも中身はiOS SDKのラッパーにつき、Objective-C や Swift を C# に置き換えるような実装になります。
Windowsでも開発が可能ですが、ビルドの際に必ずmacOSが必要になります。


Portable Class Library(PCL) / .NET Standard

これら2つは、クラスライブラリ専用のフォーマットです。

Portable Class Library(PCL)はモバイル開発でのクロスプラットフォーム機能集約を目的とした、.NET Frameworkの小さなサブセットです。

その後.NET Coreの策定により対象プラットフォームが拡大したため、PCLの後継規格として.NET Standardが作られました。
こちらもサブセットとしての基本機能範囲は小さいものの、Microsoft公式のNuGetパッケージが多く提供されており、PCLよりも難易度が下がっています。
今後最もツブシが効きそうなのは、この.NET Standardでしょうか。


.NET StandardライブラリのMSTestでハマる

DB接続の基本機能が出来たところで、MSTestを導入したんですね。
実装が大きくならないうちにテストを書いておこう、と。
そこで、なんだか良く分からないエラーが発生したので、記録しておきます。

MSTestプロジェクトを作る

MSTestプロジェクトのビルドターゲットは、.NET Frameworkのみです。
.NET Standard.NET Coreは、選択肢に出てきません。

テスト対象プロジェクトは.NET Standard 1.3で作っています。
こちらの資料に基づいて、ビルドターゲットを.NET Framework 4.6にしました。

テストプロジェクトの参照に対象プロジェクトを追加して、テストを書きました。

参考:
.NET Standard Library


テスト対象インスタンス生成時に落ちた

書いたテストを実行してみると、テスト対象インスタンスを生成する箇所で落ちました。
f:id:try_dot_net_core:20161215103644j:plain

例外メッセージは下記のとおり。

ファイルまたはアセンブリ 'System.Data.SqlClient, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'、またはその依存関係の 1 つが読み込めませんでした。
見つかったアセンブリのマニフェスト定義はアセンブリ参照に一致しません。 (HRESULT からの例外:0x80131040)

参照バイナリが読めない、と。

テストプロジェクトにも同じNuGetパッケージを読ませる必要があるかなと、NuGetパッケージを追加してみましたが、現象変わらず。

よくよくメッセージを読むと、System.Data.SqlClient, Version=4.1.0.0と。

あれ?
.NET StandardプロジェクトのNuGetパッケージバージョンは、4.3.0でした。

同じNuGetパッケージを、MSTestプロジェクトにも読ませています。
とはいえ.NET Frameworkには、標準的にSystem.Data.SqlClientが含まれているはず。
同梱dllを優先してしまうのかなぁ...?

ここまでの実装ソース


NuGetパッケージをダウングレードしただけでは、ダメ

仕方がないので、制作中ライブラリのNuGetパッケージを、エラーメッセージに記載があった4.1.0にダウングレードしました。
テストに引っ張られて実装側を変えるのはアレですが、背に腹は変えられません...。 f:id:try_dot_net_core:20161215105848j:plain

MSTestプロジェクト側のNuGetパッケージは、削除しました。

しかしこれでも... f:id:try_dot_net_core:20161215110643j:plain
現象変わらずでした。


MSTestプロジェクト側に、同じく古いパッケージを入れた

さきほど、MSTest側のNuGetパッケージは削除してしまいました。
こちらにも、テスト対象プロジェクトと同様に古いバージョンを選んで、インストールしてみます。
f:id:try_dot_net_core:20161215111245j:plain

試してみると... f:id:try_dot_net_core:20161215111500j:plain
ああ、よかった!
通りました!

んー、しかし。
なぜダメだったのか、理由がよく分からないままです。
スッキリしないなぁ。

ここまでの実装ソース


.NET CoreからDBに繋いでみる

開発環境がひと段落したので、ぼちぼちコードを書いていきます。

前々回、PCLプロジェクトを.NET Standardプロジェクトに変換しました。
ええ、既にPCLになってるヤツは、いいんです。

問題は、旧来の.NET Frameworkのソースです。
いやもう、すんげえ、しんどい。

たとえばDBアクセス。
2.0時代からDataTableが大好きだったんですが、.NET Standardプロジェクトの名前空間には、DataTableもDataRowもDataAdapterも存在しません。
うええ。しぬ...。

まあしかし。
ちょうどXamarinでSQLiteに繋ぎたくて、手持ちのDBライブラリが修正待ちでした。
色々としゃーないので、DB接続を最初から試していきます。


環境

ビルドターゲットは、.NET Standard 1.3です。
ASP.NET Coreはもとより、Xamarin.iOS/Androidでも動くことが目標です。
バージョン1.3の根拠は、SQL Server接続用NuGetパッケージSystem.Data.SqlClientの依存バージョンです。

また、Xamarinではまだ対応していないEntity Frameworkは、今回は対象外にします。

ひとまず接続対象は、SQL Serverで。
とはいえ、なるべくSystem.Data.Commonの抽象クラスを念頭に置いて実装します。
後日に基底クラスとSQL Server用クラスに分離して、MySQLSQLite のサブクラスを書く予定です。


接続、切断

まずは接続と切断。
特に以前と変わりなく、接続文字列を渡してOpenする感じですね。

var connectionString
    = string.Format("server={0};user id={1}; password={2}; database={3}; pooling=false",
        "サーバのアドレス or ホスト名",
        "DBユーザー名",
        "DBユーザーのパスワード",
        "DBインスタンス名");

var connection = new System.Data.SqlClient.SqlConnection();
connection.ConnectionString = connectionString;
connection.Open();

//何か処理する。

connection.Close();

ここまでの実装ソース


SELECTでないコマンド

INSERT とか UPDATE とかですね。
ここも変わりないです。

var connection = new System.Data.SqlClient.SqlConnection();
connection.ConnectionString = "DB接続文字列";
connection.Open();

var command = new System.Data.SqlClient.SqlCommand();
command.Connection = connection;
command.CommandText = "Non-QueryなSQL";
var result = command.ExecuteNonQuery();
command.Dispose();

connection.Close();

ここまでの実装ソース


DbDataReader

この辺は、懐かしい。
VB6のDAOとか、こんな感じで書いてました。
DataSetにFillするより、処理は早いですよね。

var connection = new System.Data.SqlClient.SqlConnection();
connection.ConnectionString = "DB接続文字列";
connection.Open();

var command = new System.Data.SqlClient.SqlCommand();
command.CommandText = "SELECT文";

var reader = command.ExecuteReader(CommandBehavior.SingleResult);
while (reader.Read())
{
    var value = reader.GetValue(0);
}
command.Dispose();

connection.Close();

ここまでの実装ソース


DataTableに変わるもの

さて、いよいよ課題に差し掛かりました。
DataTableのように、SELECTクエリの結果に応じて動的に列を生成してほしい。
カラムの型情報も保持しといてほしい。

IEnumerable<IDataRecord>をまるっと保持させようかと思ったんですが、なんかメモリをドカ食いしそうな気がします。

かっこ悪いですが、データは単なるobject配列にして。
行オブジェクトからは、カラム名で値を取得できるようにします。

DbDataReaderからはReadOnlyCollection<DbColumn>のカラム情報が取れました。
データとカラム情報だけ貰ったら、DbDataReaderは破棄してしまいましょう。

DataTableの代わり:ResultTableクラス

public class ResultTable : IDisposable
{
    private ReadOnlyCollection<DbColumn> _columns;
    public ReadOnlyCollection<DbColumn> Columns => _columns;

    private ResultRow[] _rows;
    public ResultRow[] Rows => _rows;

    private int _columnCount;
    public int ColumnCount => _columnCount;

    private int _rowCount;
    public int RowCount => _rowCount;

    private Dictionary<string, int> _columnNameIndexes;


    public ResultTable(DbDataReader reader)
    {
        if (reader == null
            || !reader.CanGetColumnSchema())
        {
            throw new ArgumentException("DbDataReader has no column schema");
        }


        //Columns
        this._columns = reader.GetColumnSchema();

        this._columnNameIndexes = new Dictionary<string, int>();
        for (var i = 0; i < this._columns.Count; i++)
            this._columnNameIndexes.Add(this._columns[i].ColumnName, i);

        this._columnCount = this._columns.Count;


        //Rows
        var rows = new List<ResultRow>();
        while (reader.Read())
            rows.Add(new ResultRow(this, reader));

        this._rows = rows.ToArray();
        this._rowCount = this._rows.Length;
    }


    public DbColumn Column(int index)
    {
        return this._columns[index];
    }


    public DbColumn Column(string columnName)
    {
        return this._columns[this.GetColumnIndex(columnName)];
    }


    public int GetColumnIndex(string columnName)
    {
        return this._columnNameIndexes[columnName];
    }


    public void Dispose()
    {
        foreach (var row in this._rows)
            row.Dispose();

        this._rows = null;

        this._columns = null;
        this._columnNameIndexes = null;
    }
}

DataRowの代わり:ResultRowクラス

public class ResultRow : IDisposable
{
    private object[] _items;
    private ResultTable _table;

    public ResultRow(ResultTable table, IDataRecord dataRecord)
    {
        this._table = table;
            
        this._items = new object[this._table.ColumnCount];
        dataRecord.GetValues(this._items);
    }


    public object Item(int index)
    {
        return this._items[index];
    }


    public object Item(string columnName)
    {
        return this._items[this._table.GetColumnIndex(columnName)];
    }


    public void Dispose()
    {
        if (this._items != null)
        {
            for (var i = 0; i < this._items.Length; i++)
                this._items[i] = null;
        }

        this._items = null;
        this._table = null;
    }
}

DbDataReaderからResultTableを取得すると、こんな感じです。

var connection = new System.Data.SqlClient.SqlConnection();
connection.ConnectionString = "DB接続文字列";
connection.Open();

var command = new System.Data.SqlClient.SqlCommand();
command.CommandText = "SELECT文";

var reader = command.ExecuteReader(CommandBehavior.SingleResult);
command.Dispose();

var resultTable = new ResultTable(reader);
reader.Dispose();

connection.Close();

ここまでの実装ソース


任意クラスの配列を返すジェネリックメソッド

自由にSELECTクエリを書けるようになりました。
しかし、C#は静的型付け言語。戻り値の型を指定出来ると便利です。

Entity FrameworkにはDataReader.AutoMap<T>というのがあるようですが...。
そういう便利メソッドは、見当たりません。

自力でマッピングするサンプルがありましたので、要所をまるっと頂いて実装しました。

public T[] Query<T>(string sql)
{
    try
    {
        var result = new List<T>();
        var props = typeof(T).GetRuntimeProperties().ToArray();
        var reader = this.GetReader(sql);  //<- SQL文字列からDbDataReaderを取得するメソッド

        var done = false;
        var matchProps = new List<PropertyInfo>();

        while (reader.Read())
        {
            if (!done)
            {
                var columnNames = new List<string>();
                for (var i = 0; i < reader.FieldCount; i++)
                    columnNames.Add(reader.GetName(i));

                matchProps.AddRange(props.Where(prop => columnNames.Contains(prop.Name)));
                done = true;
            }

            var row = Activator.CreateInstance<T>();
            foreach (var property in matchProps)
                property.SetValue(row, reader[property.Name]);

            result.Add(row);
        }

        reader.Dispose();
        return result.ToArray();
    }
    catch (Exception)
    {
        throw;
    }
}

ここまでの実装ソース

参考:
DataReader からクラスにマッピングのサンプル - Qiita


パラメータを差し込んでくれるように

いまのところ、SQL文は全て手書き前提の実装になってます。

このままだとサーバサイドをやるとき、インジェクション対策がめんどいですね。
そこで、DbParameterを使って渡し値をセットできるようにします。

SELECTクエリもNon-Queryメソッドも、実行するたびにDbCommandを生成します。
渡し値パラメータのセットは、そのロジックを切り出して共用するようにします。

private DbCommand GetCommand(DbParameter[] parameters = null)
{
    var result = new System.Data.SqlClient.SqlCommand();
    result.Connection = (System.Data.SqlClient.SqlConnection) this._connection;

    if (parameters != null
        && parameters.Length > 0)
    {
        result.Parameters.AddRange(parameters);
    }

    return result;
}

public int Execute(string sql, DbParameter[] parameters = null)
{
    var command = this.GetCommand(parameters);
    --- 以下略 ---
}

public DbDataReader GetReader(string sql, DbParameter[] parameters = null)
{
    var command = this.GetCommand(parameters);
    --- 以下略 ---
}

public ResultTable Query(string sql, DbParameter[] parameters = null)
{
    var command = this.GetCommand(parameters);
    --- 以下略 ---
}

public T[] Query<T>(string sql, DbParameter[] parameters = null)
{
    try
    {
        var result = new List<T>();
        var props = typeof(T).GetRuntimeProperties().ToArray();
        var reader = this.GetReader(sql, parameters);
        --- 以下略 ---
    }
}

ここまでの実装ソース

ひとまず、最低限のDB入出力が確認できました!


ASP.NET Coreに、手持ちのライブラリを参照させる(2)

前回で、.NET Standardプロジェクトの参照が出来ました。
このプロジェクトが吐き出す dll をNuGetパッケージ化すれば、ソースでなくバイナリの参照が出来るはずです。
今回は、パッケージ管理ツールの使い方をなぞっていきます。

ローカル用NuGetパッケージを作る

私の手持ちライブラリを、.NET Standard1.1バイナリを作るよう書き換えました。
これで作った dll を使い、ローカルで使うNuGetパッケージを作ってみます。

こちらの記載にあるNuGet Package Explorerを使います。
バイナリの配布場所は見当たりませんでしたので、GitHubからソースを頂いて Visual Studio でビルドし、exe を作ります。

起動すると、こんな感じ。
f:id:try_dot_net_core:20161211003019j:plain

3段目「Create a new package」を選ぶと、プレーンな新規データが表示されます。
f:id:try_dot_net_core:20161211003343j:plain

まずは左ペインです。
パッケージの名称やバージョン、依存関係を書き込んでいきます。
左上のメモ帳っぽいアイコンをクリックすると、編集/表示が切り替わります。
ローカル使用時に必要なのは上端のID,バージョン、画面下部の依存関係です。
なおID項目は、公開されているNuGetパッケージと重複するものを避けてください。 依存解決時に、意図しないパッケージ情報を参照してしまう現象がありました。 f:id:try_dot_net_core:20161211005920j:plain

依存関係は、下端部「Edit dependencies」から出てくる画面で編集します。
f:id:try_dot_net_core:20161211010612j:plain

.NET Standardプロジェクトの依存ライブラリは、project.jsonファイルに書かれています。

{
  "supports": {},
  "dependencies": {
    "Microsoft.NETCore.Portable.Compatibility": "1.0.1",
    "NETStandard.Library": "1.6.0",
    "System.Net.Requests": "4.3.0",
    "System.Xml.XmlSerializer": "4.3.0"
  },
  "frameworks": {
    "netstandard1.1": {}
  }
}

ライブラリリスト下端左ボタンからライブラリ検索画面を開き、名称で絞り込んで選びながら追加していきます。
f:id:try_dot_net_core:20161211010958j:plain

Target framework項目は、半角スペースとバージョンの小数点を消し、.NETStandard11とするようです。
この項目の名称ルールは、何だか良くわかりません。
前回調べたJson.NETNewtonsoft.Json.nuspecファイル記述からコピペしました。
画面上の末尾小数点は消えてしまいますが、生成されるパッケージの定義ファイルには書き込まれています。
上のjsonにある依存ライブラリを全部書き込むと、下記のようになります。
f:id:try_dot_net_core:20161211011650j:plain

左ペインが完成すると、こんな感じ。
f:id:try_dot_net_core:20161211011827j:plain

次は右ペインです。
右側の空白箇所を右クリックすると、追加するフォルダリストが出てきます。
ルートにLibフォルダを追加します。
f:id:try_dot_net_core:20161211012341j:plain

libを右クリックし、.NET Standard1.1用のフォルダを作ります。
f:id:try_dot_net_core:20161211012554j:plain

Visual Studioでビルドした dll を、.NET Standard1.1用フォルダに配置します。 f:id:try_dot_net_core:20161211013906j:plain

これで、パッケージ作成準備完了です。
f:id:try_dot_net_core:20161211013210j:plain

あとは保存すれば、
f:id:try_dot_net_core:20161211014318j:plain

任意の場所にNuGetパッケージファイルが作られます。
f:id:try_dot_net_core:20161211094833j:plain

作られたNuGetパッケージファイルを、社内の共有フォルダに置いておけば、
複数人が参照するライブラリをNuGetで管理できるようになりますね。



参考:
Nugetの使い方とパッケージの作り方 - Qiita
GitHub - NuGetPackageExplorer/NuGetPackageExplorer: The new home for NuGetPackageExplorer (moved from https://npe.codeplex.com)


ローカルのNuGetフォルダからインストールする

次は、上で作ったNuGetパッケージを読み込む側の操作です。
ソリューションエクスプローラ上のプロジェクトを右クリック→「NuGetパッケージの管理」を選びます。 f:id:try_dot_net_core:20161211112257j:plain

まず、上で作った自作NuGetパッケージを置いてあるソースフォルダを指定します。
画面右上、パッケージソース右側の歯車をクリック、ソース編集画面を開きます。
f:id:try_dot_net_core:20161211112513j:plain

右上の「+」ボタンでパッケージソース行を追加し、名前とソースパスを書き込みます。
f:id:try_dot_net_core:20161211112813j:plain

ソース編集を終えて戻てくると、NuGetのパッケージソースドロップダウンから、 先ほど追加したものが選択できるようになります。
f:id:try_dot_net_core:20161211113135j:plain

中央上の[参照][インストール済み][更新プログラム]は、フィルタになっています。
[参照]を選ぶと、上で作ったパッケージがリストに表示されます。
f:id:try_dot_net_core:20161211113504j:plain

選択してインストールボタンをクリックすると、取り込んでくれます。
f:id:try_dot_net_core:20161211125922j:plain

参照項目に追加され、使えるようになりました!
f:id:try_dot_net_core:20161211130332j:plain


ASP.NET Coreに、手持ちのライブラリを参照させる(1)

永らく業界に身を置けば、多少なりと手持ちのコード資産があると思います。
私も、.NET Framework2.0からメンテしているライブラリがあります。
.NET Coreでは手持ち資産をどうやって使うのか、試してみます。


PCLのdll/プロジェクトを参照してみる

私の手持ちの中に、Xamarin用にPCL(Portable Class Library)化したものがありました。
ポータブルなライブラリーと銘打ってるんですが、試してみると...。
f:id:try_dot_net_core:20161209195612j:plain
ダメなんですね。

.NET Core projects only support referencing .NET framework assemblies in this release.
To reference other assemblies, they need to be included in a NuGet package and reference that package.

んー、.NET Core標準でないヤツは、NuGetパッケージじゃないと参照させないよ、と。
おおぅ、そりゃ無いぜハニー。

では、プロジェクト参照だとどうでしょう?
これも、参照の追加操作は可能なんですが...
f:id:try_dot_net_core:20161209201909j:plain

実際に使おうとすると、読めてないんですね。
f:id:try_dot_net_core:20161209202622j:plain

やーこれは、NuGetパッケージを作らないと、ダメなのか...。


NuGetパッケージについて調べる

NuGetパッケージはXamarinで色々使わせて頂き、お世話になりました。
とはいえ、自分で作ったことはありませんので、作り方を調べます。

このあたりを起点に、パッケージの構造GUIツールの使い方を見て試行錯誤。
PCLのdllをパッケージ化し、読ませてみましたが...
f:id:try_dot_net_core:20161210113507j:plain

Errors in C:\Users\XXXXXX\Documents\Visual Studio 2015\Projects\dotnet_sample\src\WebApplication1\WebApplication1.xproj
    Package Xb.Core 1.0.0 is not compatible with netcoreapp1.1 (.NETCoreApp,Version=v1.1). Package Xb.Core 1.0.0 supports: net (.NETFramework,Version=v0.0)
    One or more packages are incompatible with .NETCoreApp,Version=v1.1.

incompatible with .NETCoreApp,Version=v1.1.ですか...。
PCLとしてビルドしているのが、そもそもNGのようです。

すると、利用できるパッケージのビルドターゲットって、何になってるんでしょう?
Xamarinでお世話になったJson.NETは、ASP.NET Coreでも使えました。
パッケージをバラして、中身を見てみます。

パッケージファイルnewtonsoft.json.9.0.1.nupkgをダウンロードして解凍します。
NuGetパッケージはファイル一式をzipしたものなので、アーカイバで解凍できます。

パッケージの定義XMLNewtonsoft.Json.nuspecを紐解きますと...

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
  <metadata>
    <id>Newtonsoft.Json</id>
    <version>9.0.1</version>
    <title>Json.NET</title>
    <authors>James Newton-King</authors>
    <owners>James Newton-King</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <licenseUrl>https://raw.github.com/JamesNK/Newtonsoft.Json/master/LICENSE.md</licenseUrl>
    <projectUrl>http://www.newtonsoft.com/json</projectUrl>
    <iconUrl>http://www.newtonsoft.com/content/images/nugeticon.png</iconUrl>
    <description>Json.NET is a popular high-performance JSON framework for .NET</description>
    <language>en-US</language>
    <tags>json</tags>
    <dependencies>
      <group targetFramework=".NETFramework4.5" />
      <group targetFramework=".NETFramework4.0" />
      <group targetFramework=".NETFramework3.5" />
      <group targetFramework=".NETFramework2.0" />
      <group targetFramework=".NETPortable4.5-Profile259" />
      <group targetFramework=".NETPortable4.0-Profile328" />
      <group targetFramework=".NETStandard1.0">
        <dependency id="Microsoft.CSharp" version="4.0.1" />
 --- 中略 ---
        <dependency id="System.Xml.XDocument" version="4.0.11" />
      </group>
    </dependencies>
  </metadata>
</package>

んー?.NETStandard1.0ってナンデスカソレ?
あやしい!



参考:
Nugetの使い方とパッケージの作り方 - Qiita
プライベートなnugetパッケージを作る - Qiita
VS100 - 032 NuGet パッケージ の作成と配布 - YouTube
NuGet Gallery | Json.NET 9.0.1

.NET Standardについて調べる

さてコイツ、何者なんでしょう?
公式ドキュメントが日本語なので、ありがたく目を通します。

次世代のPCL、ですって。
PCLはMicrosoftプラットフォーム縛り、.NET Standardはプラットフォームフリーとな。
やはり、コレでビルドするのがいいみたいですね。

さてしかし。
Visual Studioのプロジェクトテンプレートには、.NET Standard が見当たりません。
どうやって作るのよ?

はい、ぐぐりました。
これなんか、モロですね。
内容はこちらと同じで、要はPCLプロジェクトを作って変換しろ、とのことです。

ドキュメントでは、変換用のリンクをクリックする、とあります。
そんなんあったけなぁ?と、PCLプロジェクトのプロパティを開きますと...
f:id:try_dot_net_core:20161210123433j:plain
右下の青字下線でTarget .NET Platform Standardと。これか。



参照:
c# - How to create .NET Platform Standard project - Stack Overflow
プロジェクトを整理し、.NET Framework と .NET Core をサポートする

.NET Standardプロジェクトをビルドする

変換リンクをクリックし、注意喚起のメッセージボックスで「はい」を選ぶと。
f:id:try_dot_net_core:20161210130233j:plain
おお!ビルドターゲットが変わってます。

しかし、このままビルドすると...
f:id:try_dot_net_core:20161210130358j:plain
エラーもりだくさん!

しかしこれ、ほとんどが.NET標準ライブラリの参照不足エラーでした。
私のPCLの場合、Xamarin.Formsで広く使うことが目的でしたので、外部の依存ライブラリが無かったことが幸いしました。

早速、参照を追加してやります。
参照編集はASP.NET Coreプロジェクトと同様に、ファイル単位では出来ません。
「NuGetパッケージの管理」から、名前空間を指定して、お目当てのブツを頂きます。
f:id:try_dot_net_core:20161210131045j:plain

パッケージをインストールするたびに復元が走り、参照エラーが消えていきます。
全部消えたところで、ビルドすると... f:id:try_dot_net_core:20161210132652j:plain
とおりました!


.NET Standardプロジェクトを参照させてみる

さて、PCLよりもカバー範囲が広い、.NET Standardプロジェクトが出来ました。
これだと、プロジェクト参照は通るんでしょうか...?

ソリューションに.NET Standardプロジェクトを追加して、ASP.NET Coreプロジェクトから.NET Standardプロジェクトを参照するようにします。 f:id:try_dot_net_core:20161210134202j:plain

さて、参照プロジェクトは、読めてますかね?
f:id:try_dot_net_core:20161210134551j:plain
読んでくれました!


次は、NuGetパッケージを作ってみます。