音楽サーバ"Mopidy"のフロントエンドを作る:01 AspCore+TSの環境作り
ここを最後に更新してから、はや2年半が過ぎました。
.Net Coreはそろそろ3.0が出るかというこの頃。初期の粗削りさは影を潜め、環境もドキュメントもリッチになり、とても便利になりました。
個人的に少し時間とモチベーションが出来たので、現在の.Net Core環境をベースに、アプリ作りの過程を記事に起こしてみようと思います。
なお、アプリ自体は既に出来上がっており、こちらに公開しております。
Mopidyとは?
Mopidyとは、Linux上で動く音楽サーバです。
純粋なサーバ実装で、UIはありません。フロントエンドは各自好きなExtensionを入れてね、というスタンスです。
フロントエンドとしては、Iris、Mopedなどが有名です。
既に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が動作してるよね、というところを確認。
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のロジックが止まってくれます。
生成済みの 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
で動くように少々手を加えたものです。
SharpCifs
はJCIFS
の移植版なので、移植の移植といったところ。
公開にあたっては、SharpCifs
作者のJ. Arturoさんに快諾頂き、嬉しい限りです!
インストール
パッケージをnuget.org
に公開しているので、Visual Studio のNuGetパッケージマネージャ
からインストールできます。
プレリリース版につき、「プレリリースを含める」チェックをONにしてください。
使い方
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/
こんな感じに動きます
試しに、共有フォルダ内のリストを取ってきます。
アクセス先はこちら。
おおむねこんなコードを書きます。
日時は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
Xamarin.iOS
.NET Core
各プラットフォームとも、ファイル、ディレクトリどちらも取れてます!
文字化け無く、日時も正確ですね。
参考リンク
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
従来のいわゆる「ドットネット」といえば、こちら。
対象プラットフォームは、Windows
だけです。
クラスライブラリとして参照可能なフォーマットは、.NET Framework
/ .NET Standard
/ Portable Class Library(PCL)
の3種類です。
SI業界にはお馴染みですね。
歴史が長いため実績も情報も多く、サードパーティライブラリが充実しています。
以下のフォーマットは、この.NET Framework
のサブセットを基本としています。
.NET Core
本ブログのテーマ、.NET Core
です。
Windows
/ Linux
/ macOS
の3つのプラットフォームで動作します。
クラスライブラリとして参照可能なフォーマットは、.NET Core
/ .NET Standard
の2種類だけです。
.NET Framework
のうち主要部分の多くが移植されており、Windowsプログラマにとっては敷居が低くなっています。
Windows.Forms
やWPF
などのUI関連機能はオミットされており、Webアプリケーション / API などのサーバサイドがターゲットになりそうです。
今回紹介する中では最後発のフォーマットになるため、まだ日本語情報が少なく、外部ライブラリの対応も現在進行中といったところです。
Xamarin.Forms
Microsoftが先だって買収したXamarin
の中でも、最も意欲的なフォーマットです。
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
を対象としたフォーマットです。
対象プラットフォームはAndroid
のみ。
しかし参照可能なクラスライブラリフォーマットは多く、.NET Standard
/ Portable Class Library(PCL)
/ Xamarin.Android
/ .NET Framework
の4種類です。
ただし.NET Framework
の全機能が使えるわけではなく、バイナリ参照の際はビルドが通っても実行時に落ちることがあり、注意が必要です。
.NET Framework
サブセットとしては、Portable Class Library(PCL)
より広い範囲の機能が用意されています。
中身はAndroid SDK
のラッパーなので、C# で実装するというよりは、Android-Java を C# に翻訳するように書き進めることになります。
Xamarin.iOS
Xamarin
の中で、iOS
を対象としたフォーマットです。
当然ながら対象プラットフォームは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
にしました。
テストプロジェクトの参照に対象プロジェクトを追加して、テストを書きました。
テスト対象インスタンス生成時に落ちた
書いたテストを実行してみると、テスト対象インスタンスを生成する箇所で落ちました。
例外メッセージは下記のとおり。
ファイルまたはアセンブリ '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
にダウングレードしました。
テストに引っ張られて実装側を変えるのはアレですが、背に腹は変えられません...。
MSTestプロジェクト側のNuGetパッケージは、削除しました。
しかしこれでも...
現象変わらずでした。
MSTestプロジェクト側に、同じく古いパッケージを入れた
さきほど、MSTest側のNuGetパッケージは削除してしまいました。
こちらにも、テスト対象プロジェクトと同様に古いバージョンを選んで、インストールしてみます。
試してみると...
ああ、よかった!
通りました!
んー、しかし。
なぜダメだったのか、理由がよく分からないままです。
スッキリしないなぁ。
.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用クラスに分離して、MySQL や SQLite のサブクラスを書く予定です。
接続、切断
まずは接続と切断。
特に以前と変わりなく、接続文字列を渡して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 を作ります。
起動すると、こんな感じ。
3段目「Create a new package」を選ぶと、プレーンな新規データが表示されます。
まずは左ペインです。
パッケージの名称やバージョン、依存関係を書き込んでいきます。
左上のメモ帳っぽいアイコンをクリックすると、編集/表示が切り替わります。
ローカル使用時に必要なのは上端のID
,バージョン
、画面下部の依存関係
です。
なおID
項目は、公開されているNuGetパッケージと重複するものを避けてください。
依存解決時に、意図しないパッケージ情報を参照してしまう現象がありました。
依存関係
は、下端部「Edit dependencies」から出てくる画面で編集します。
.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": {} } }
ライブラリリスト下端左ボタンからライブラリ検索画面を開き、名称で絞り込んで選びながら追加していきます。
Target framework項目は、半角スペースとバージョンの小数点を消し、.NETStandard11
とするようです。
この項目の名称ルールは、何だか良くわかりません。
前回調べたJson.NET
のNewtonsoft.Json.nuspec
ファイル記述からコピペしました。
画面上の末尾小数点は消えてしまいますが、生成されるパッケージの定義ファイルには書き込まれています。
上のjsonにある依存ライブラリを全部書き込むと、下記のようになります。
左ペインが完成すると、こんな感じ。
次は右ペインです。
右側の空白箇所を右クリックすると、追加するフォルダリストが出てきます。
ルートにLib
フォルダを追加します。
lib
を右クリックし、.NET Standard1.1
用のフォルダを作ります。
Visual Studioでビルドした dll を、.NET Standard1.1
用フォルダに配置します。
これで、パッケージ作成準備完了です。
あとは保存すれば、
任意の場所にNuGetパッケージファイル
が作られます。
作られたNuGetパッケージファイルを、社内の共有フォルダに置いておけば、
複数人が参照するライブラリをNuGetで管理できるようになりますね。
参考:
Nugetの使い方とパッケージの作り方 - Qiita
GitHub - NuGetPackageExplorer/NuGetPackageExplorer: The new home for NuGetPackageExplorer (moved from https://npe.codeplex.com)
ローカルのNuGetフォルダからインストールする
次は、上で作ったNuGetパッケージを読み込む側の操作です。
ソリューションエクスプローラ上のプロジェクトを右クリック→「NuGetパッケージの管理」を選びます。
まず、上で作った自作NuGetパッケージを置いてあるソースフォルダを指定します。
画面右上、パッケージソース右側の歯車をクリック、ソース編集画面を開きます。
右上の「+」ボタンでパッケージソース行を追加し、名前とソースパスを書き込みます。
ソース編集を終えて戻てくると、NuGetのパッケージソースドロップダウンから、
先ほど追加したものが選択できるようになります。
中央上の[参照][インストール済み][更新プログラム]は、フィルタになっています。
[参照]を選ぶと、上で作ったパッケージがリストに表示されます。
選択してインストールボタンをクリックすると、取り込んでくれます。
参照項目に追加され、使えるようになりました!
ASP.NET Coreに、手持ちのライブラリを参照させる(1)
永らく業界に身を置けば、多少なりと手持ちのコード資産があると思います。
私も、.NET Framework2.0からメンテしているライブラリがあります。
.NET Coreでは手持ち資産をどうやって使うのか、試してみます。
PCLのdll/プロジェクトを参照してみる
私の手持ちの中に、Xamarin用にPCL(Portable Class Library)化したものがありました。
ポータブルなライブラリーと銘打ってるんですが、試してみると...。
ダメなんですね。
.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パッケージじゃないと参照させないよ、と。
おおぅ、そりゃ無いぜハニー。
では、プロジェクト参照だとどうでしょう?
これも、参照の追加操作は可能なんですが...
実際に使おうとすると、読めてないんですね。
やーこれは、NuGetパッケージを作らないと、ダメなのか...。
NuGetパッケージについて調べる
NuGetパッケージはXamarinで色々使わせて頂き、お世話になりました。
とはいえ、自分で作ったことはありませんので、作り方を調べます。
このあたりを起点に、パッケージの構造やGUIツールの使い方を見て試行錯誤。
PCLのdllをパッケージ化し、読ませてみましたが...
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プロジェクトのプロパティを開きますと...
右下の青字下線でTarget .NET Platform Standard
と。これか。
参照:
c# - How to create .NET Platform Standard project - Stack Overflow
プロジェクトを整理し、.NET Framework と .NET Core をサポートする
.NET Standardプロジェクトをビルドする
変換リンクをクリックし、注意喚起のメッセージボックスで「はい」を選ぶと。
おお!ビルドターゲットが変わってます。
しかし、このままビルドすると...
エラーもりだくさん!
しかしこれ、ほとんどが.NET標準ライブラリの参照不足エラーでした。
私のPCLの場合、Xamarin.Formsで広く使うことが目的でしたので、外部の依存ライブラリが無かったことが幸いしました。
早速、参照を追加してやります。
参照編集はASP.NET Coreプロジェクトと同様に、ファイル単位では出来ません。
「NuGetパッケージの管理」から、名前空間を指定して、お目当てのブツを頂きます。
パッケージをインストールするたびに復元が走り、参照エラーが消えていきます。
全部消えたところで、ビルドすると...
とおりました!
.NET Standardプロジェクトを参照させてみる
さて、PCLよりもカバー範囲が広い、.NET Standardプロジェクトが出来ました。
これだと、プロジェクト参照は通るんでしょうか...?
ソリューションに.NET Standardプロジェクトを追加して、ASP.NET Coreプロジェクトから.NET Standardプロジェクトを参照するようにします。
さて、参照プロジェクトは、読めてますかね?
読んでくれました!