Try .NET Core

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

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パッケージを作ってみます。

ASP.NET Coreサンプルアプリ「MusicStore」を動かす

開発環境が一通り整いましたので、実際に開発の方を試したくなります。

テンプレートのコードを眺めると、MVCフレームワークを1つ2つ齧ったことがあれば、ざっくりとした挙動は想像できます。

しかし、もう少し、情報が欲しい。

Web案件では主にPHPで、業務系案件は古臭いWebフォームを扱うばかりだった私は、ASP.NET MVCというMicrosoftご謹製フレームワークについて、全く知識がありません。

なにか手頃な、そこそこ主だった実装があるサンプルコードが無いかと探したら、「MusicStore」というサンプルに行き着きました。

aspnet/MusicStore Sample MusicStore application that uses MVC and Entity Framework.
f:id:try_dot_net_core:20161207220252j:plain

音楽ショップのサンプルということで、ユーザー認証やカート機能など、実践的なコードが読めそうです!
早速ソースを頂いて、Visual Studioで開いてみます。

実行してみる

README.mdに実行の仕方が書いてあるんですが、

If you have Visual Studio 2015
   i. Open MusicStore.sln in Visual Studio 2015 and run the individual applications on IIS Express.

やー。ずいぶん、ざっくりっすね~。
DBの記述が見当たりません。
要らないってことも無いよね、と思いつつ。

パッケージ復元とビルドは、本記事記述時最新の2016年11月24日コミットの状態で、何事もなく通りました。

別環境でパッケージ復元にハマったことがありましたが、その際はNugetパッケージの管理画面でプレリリースを含めるチェックボックスを入れて検索することで、無事にインストール出来ました。
f:id:try_dot_net_core:20161208215916j:plain

README.md上では/src/MusicStore/のパスが散見されるので、これがメインプロジェクトなのでしょう。
f:id:try_dot_net_core:20161208215120j:plain

実行すると。
f:id:try_dot_net_core:20161208220325j:plain
例外ですねー...。

例外を解析する

詳細表示すると、InnerExceptionのStackTraceに発生位置がありました。
f:id:try_dot_net_core:20161208221055j:plain
Models\SampleData.cs:行 26とな。

該当箇所はこんな感じです。
f:id:try_dot_net_core:20161208221854j:plain
EnsureCreateAsyncですって。非同期で確実に生成するぜ、と。
その後に続くロジックが、InsertTestDataCreateAdminUserとのことですんで、どうやらDBインスタンスを新規作成してるようですね。

なにか設定ファイルにヒントが無いかと、プロジェクト内をみてみます。
config.jsonというファイルに、DBの接続文字列っぽいのがありました。
f:id:try_dot_net_core:20161208222324j:plain

中身はこちら

{
  "AppSettings": {
    "SiteTitle": "ASP.NET MVC Music Store",
    "CacheDbResults": true
  },
  "DefaultAdminUsername": "Administrator@test.com",
  "DefaultAdminPassword": "YouShouldChangeThisPassword1!",
  "Data": {
    "DefaultConnection": {
      // Use a shared (and running) LocalDB database when executing in IIS e.g.
      // "Server=(localdb)\\.\\IIS_DB;Database=MusicStore;Trusted_Connection=False;MultipleActiveResultSets=true;User ID=iis_login;Password=********"
      "ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=MusicStore;Trusted_Connection=True;MultipleActiveResultSets=true;Connect Timeout=30;"
    }
  }
}

どうも、ローカルのSQL Server上のMusicStoreというインスタンスに繋ごうとしてる?
お望みどおり(?)、SQL Serverを起動して、MusicStoreインスタンスを作ります。
インスタンスだけの状態で、テーブルもデータも空っぽです。
f:id:try_dot_net_core:20161208223001j:plain

config.jsonの接続文字列を書き換えて、ローカルSQL Serverに繋がるようにします。

{
  "AppSettings": {
    "SiteTitle": "ASP.NET MVC Music Store",
    "CacheDbResults": true
  },
  "DefaultAdminUsername": "Administrator@test.com",
  "DefaultAdminPassword": "YouShouldChangeThisPassword1!",
  "Data": {
    "DefaultConnection": {
      // Use a shared (and running) LocalDB database when executing in IIS e.g.
      // "Server=(localdb)\\.\\IIS_DB;Database=MusicStore;Trusted_Connection=False;MultipleActiveResultSets=true;User ID=iis_login;Password=********"
      "ConnectionString": "Server=localhost;Database=MusicStore;Trusted_Connection=True;MultipleActiveResultSets=true;Connect Timeout=30;User ID=[ユーザーID];Password=[パスワード]"
    }
  }
}

jsonなのに、コメントアウト出来るんですね。
便利だけど、いいのかなMSさん...(困惑)

無事に動いた!

あらためて、Visual Studioから実行すると... f:id:try_dot_net_core:20161208223959j:plain
きました!
トップにMen at Workを持ってくるあたり、趣味が合いそうです。

ユーザー登録やログイン、カート処理など、一通り動くようです。
f:id:try_dot_net_core:20161208224449j:plain

今後はこのソースを研究しようと思います!