Azure DocumentDB Emulatorを使ってみた。で、.NET Coreから操作した話。

技術者としての尊敬の対象である Scott Hanselman 氏のブログで「Azure DocumentDB Emulator」についての記事が書かれていたので、自分でも使ってみました。

www.hanselman.com

こんなことをやった

「Azure DocumentDB」は、もはや、広く知られた Azureが提供する NoSQL データベースです。
いわゆる「ちょー早くて、ちょースケーラブルで、RDBの概念は捨ててから使ってね」っていうデータベースですね。
NoSQLは、その登場以来、どこにどう使うべきか?という部分についての議論・検討が行われ、まだまだ一般化していないように思います。
私自身も実は会社の業務としてNoSQLを採用したプロジェクトに関わった事はありません・・・

まあ、それはさておき(また、本投稿はNoSQLの本質を議論する趣旨ではないので・・・)、今回は以下のことを試してみたので、それらをまとめておこうと思います。

  • 「Azure DocumentDB Emulator」をインストールしてみた
  • 「.NET Core Consoleアプリ」から「Azure DocumentDB Emulator」に接続してデータ操作してみた
  • 「Azure DocumentDB(クラウド)」から「Azure DocumentDB Emulator(ローカル)」にデータをエクスポート(リストア)してみた
  • データのコンソール出力にはSerilogを使った

「Azure DocumentDB Emulator」のインストール

さあ、今回の主役である「Azure DocumentDB Emulator」をインストールします。
この「Azure DocumentDB Emulator」は、2016/11にリリースされました。
これは クラウドのAzure DocumentDB をローカルPC上でエミュレートするソフトウェアです。オフライン状態でも、Azure DocumentDB開発が可能になります。
以下のリンクページ 上部の「Download the Emulator」をクリックするとインストーラをダウンロードする事が出来ます。
(ちなみにこれはWindows版のみでMac版は残念ながらありません)

docs.microsoft.com

ダウンロードした msi ファイルは、ダブルクリックし「Next」や「OK」や、指示通りにクリックしていればインストール完了します。
インストールが完了すると、自動的に起動し、タスクトレイに以下のようなアイコンが表示されます(スタートメニューから「DocumentDB Emulator」を選択しても明示的に起動する事が出来ます)。

f:id:daigo-knowlbo:20161208015000p:plain

また、このアイコンをマウス右クリックすると以下のようなメニューが表示されます。

f:id:daigo-knowlbo:20161208015205p:plain

「Open Data Explorer...」をクリックすると以下のような管理画面が表示されます(インストール直後は、この画面も自動で表示されるはずです)。

f:id:daigo-knowlbo:20161208015308p:plain

見てわかるように「https://localhost:8081/_explorer/index.html#」という8081ポートでホストされた管理コンソール画面となります。
「.NET / .NET Core / Java...」等のタブが有り、ここに表示されている「Code Samples」をクリックすると、何も考えなくても「ビルド→実行」が可能なサンプルプロジェクトをダウンロード可能です。
画面上部の「Explorer」タブをクリックすると、DocumentDB Emulatorに保存されているデータベース内容を確認する事が出来ます(以下の画面)。
初期状態では 空 になります。

f:id:daigo-knowlbo:20161208015816p:plain

「.NET Core Consoleアプリ」+「Azure DocumentDB Emulator」でデータ保存・読み込み

では、せっかくなので最新の Visual Studio 2017 RC + .NET Core を使って DocumentDB Emulator に接続してみます。

①プロジェクトの作成

Visual Studio 2017 RCを起動し、メニュー「ファイル→新規作成→プロジェクト」を選択します。

f:id:daigo-knowlbo:20161208020502p:plain

ここではシンプルに、テンプレートとして「Console App(.NET Core)」を選択し、プロジェクト名は「DocDbExampleCoreConsole」としました。

②Nuget参照の追加

次にNugetで必要なライブラリへの参照を追加します。
メニュー「プロジェクト→Nuget パッケージの管理」を選択します。
まず必要なのは「Microsoft.Azure.DocumentDB.Core」になります。

f:id:daigo-knowlbo:20161208021044p:plain

さらに、これは本投稿の本質ではありませんが、コンソールへのデータログ出力用に「Serilog」および、そのコンソールSinkである「Serilog.Sinks.Console」も追加します。

f:id:daigo-knowlbo:20161208021059p:plain

f:id:daigo-knowlbo:20161208020914p:plain

③モデルクラスの作成

DocumentDBに保存するモデルクラスを作成します。
ブログの投稿内容を想定したモデルを作成することとします。
メニュー「プロジェクト→クラスの追加」を選択し、表示された「新しい項目の追加」ウィンドウで「コード→クラス」を選択し、名前は「BlogPost.cs」とします。

f:id:daigo-knowlbo:20161208022754p:plain

実装は以下のとおりであり、ブログ投稿を表す「BlogPostクラス」、ブログ投稿者を表す「Userクラス」、ブログ投稿へのコメントを表す「Commentクラス」から構成されます。

// BlogPost.cs
using System.Collections.Generic;
using Newtonsoft.Json;

namespace DocDbExampleCoreConsole
{
  /// <summary>
  /// ブログポスト(1投稿)を表すクラスです。
  /// </summary>
  public class BlogPost
  {
    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }

    [JsonProperty(PropertyName = "title")]
    public string Title { get; set; }

    [JsonProperty(PropertyName = "contents")]
    public string Contents { get; set; }

    [JsonProperty(PropertyName = "author")]
    public User Author { get; set; }

    [JsonProperty(PropertyName = "comments")]
    public List<Comment> Comments { get; set; }
  }

  /// <summary>
  /// ブログ投稿者を表すクラスです。
  /// </summary>
  public class User
  {
    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }

    [JsonProperty(PropertyName = "name")]
    public string Name { get; set; }
  }

  /// <summary>
  /// ブログ投稿へのコメントを表すクラスです。
  /// </summary>
  public class Comment
  {
    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }

    [JsonProperty(PropertyName = "text")]
    public string Text { get; set; }
  }
}

リポジトリクラスの作成

モデルクラスをDocumentDBに出し入れするためのデータベースアクセス用のリポジトリクラスを作成します。
メニュー「プロジェクト→クラスの追加」を選択し、表示された「新しい項目の追加」ウィンドウで「コード→クラス」を選択し、名前は「BlogPostRepository.cs」とします。

f:id:daigo-knowlbo:20161208023254p:plain

実装は以下の通りです。

// BlogPostRepository.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Linq;

namespace DocDbExampleCoreConsole
{
  public class BlogPostRepository
  {
    /// <summary>
    /// 任意のデータベースID
    /// </summary>
    private static readonly string DatabaseId = "Blog";

    /// <summary>
    /// 任意のコレクションID
    /// </summary>
    private static readonly string CollectionId = "BlogPost";

    /// <summary>
    /// エンドポイント
    /// </summary>
    private static readonly string EndPoint = "https://localhost:8081/";

    /// <summary>
    /// 認証キー(固定)
    /// </summary>
    private static readonly string AuthKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";

    private static DocumentClient client;

    /// <summary>
    /// ブログポストデータを作成します。
    /// </summary>
    /// <param name="blogPost"></param>
    /// <returns></returns>
    public static async Task<Document> CreateBlobPostAsync(BlogPost blogPost)
    {
      return await client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), blogPost);
    }

    /// <summary>
    /// すべてのブログポストデータを取得します。
    /// </summary>
    /// <returns></returns>
    public static async Task<IEnumerable<BlogPost>> GetAllBlogPostsAsync()
    {
      IDocumentQuery<BlogPost> query = client.CreateDocumentQuery<BlogPost>(
        UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
        new FeedOptions { MaxItemCount = -1 })
        .AsDocumentQuery();

      List<BlogPost> results = new List<BlogPost>();
      while (query.HasMoreResults)
      {
        results.AddRange(await query.ExecuteNextAsync<BlogPost>());
      }

      return results;
    }

    /// <summary>
    /// データベース・コレクションの初期化を行います。
    /// </summary>
    public static void Initialize()
    {
      client = new DocumentClient(new Uri(EndPoint), AuthKey, new ConnectionPolicy { EnableEndpointDiscovery = false });
      CreateDatabaseIfNotExistsAsync().Wait();
      CreateCollectionIfNotExistsAsync().Wait();
    }
    
    /// <summary>
    /// 存在しなければデータベースを作成します。
    /// </summary>
    /// <returns></returns>
    private static async Task CreateDatabaseIfNotExistsAsync()
    {
      try
      {
        await client.ReadDatabaseAsync(UriFactory.CreateDatabaseUri(DatabaseId));
      }
      catch (DocumentClientException e)
      {
        if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
        {
          await client.CreateDatabaseAsync(new Database { Id = DatabaseId });
        }
        else
        {
          throw;
        }
      }
    }

    /// <summary>
    /// 存在しなければコレクションを作成します。
    /// </summary>
    /// <returns></returns>
    private static async Task CreateCollectionIfNotExistsAsync()
    {
      try
      {
        await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId));
      }
      catch (DocumentClientException e)
      {
        if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
        {
          await client.CreateDocumentCollectionAsync(
            UriFactory.CreateDatabaseUri(DatabaseId),
            new DocumentCollection { Id = CollectionId },
            new RequestOptions { OfferThroughput = 1000 });
        }
        else
        {
          throw;
        }
      }
    }
  }
}

リポジトリクラス実装のポイントは以下の通りです。

  • DatabaseId / CollectionId
    これらは任意の名称をつけます。ここでは「Blog」「BlogPost」としました。

  • EndPoint
    エンドポイントはデフォルトでは「https://localhost:8081/」となります。

  • AuthKey
    Azure DocumentDB Emulator では認証キーは固定で「C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==」と決められています。

  • Microsoft.Azure.Documents.Client.DocumentClient
    DocumentDBへの接続クライアント(繋ぎ役)は「DocumentClientクラス」となります。本サンプルでは Initialize() メソッド内で初期化を行っています。

  • Initialize() / CreateDatabaseIfNotExistsAsync() / CreateCollectionIfNotExistsAsync()
    データベースは、データ操作を行う前に初期作成を行う必要があります(RDBでいうところのCREATE DATABASE / CREATE TABLE)。これに該当する処理をCreateDatabaseIfNotExistsAsync() / CreateCollectionIfNotExistsAsync()で実装しています。両メソッドは Initialize() から呼び出されています。つまり、初期化処理として BlogPostRepository.Initialize() を呼び出す必要があります。

  • CreateBlobPostAsync(BlogPost blogPost)
    ブログポスト(ドキュメント)をデータベース コレクション」に追加します(RDBのINSERT)。

  • GetAllBlogPostsAsync()
    登録されたすべてのブログポスト(ドキュメント)を取得しています(RDBのSELECT)。

⑤main()の実装(リポジトリクラスの呼び出し)

用意したリポジトリクラス(BlogPostRepository)を利用して、BlogPostモデルクラスのデータベースへの出し入れ処理を実装します。
以下がコンソールアプリケーションのmain処理の実装になります。

// Program.cs
using System.Threading.Tasks;
using System.Collections.Generic;

using Serilog;

namespace DocDbExampleCoreConsole
{
  class Program
  {

    private IEnumerable<BlogPost> blogPosts = null;

    static void Main(string[] args)
    {
      Program program = new Program();

      // データベースを初期化
      program.InitializeDatabase();

      // データを挿入
      program.InitializeData().Wait();

      // データを抽出
      program.GetAllBlogPosts().Wait();

      // 抽出したデータをコンソール出力
      Log.Logger = new LoggerConfiguration()
        .WriteTo.Console()
        .CreateLogger();
      foreach (var blogPost in program.blogPosts)
      {
        Log.Information("{@BlogPost}", blogPost);
      }

      System.Console.ReadLine();
    }

    /// <summary>
    /// データベース・コレクションを初期化します。
    /// </summary>
    private void InitializeDatabase()
    {
      // データベース・コレクションを作成
      BlogPostRepository.Initialize();
    }

    /// <summary>
    /// BlogPostサンプルデータを作成します。
    /// </summary>
    /// <returns></returns>
    private async Task InitializeData()
    {
      // 50件、テスト投稿を作成
      for (int i = 0; i < 50; i++)
      {
        BlogPost blogPost = new BlogPost() {
          Title = string.Format("Title {0}", i),
          Contents = string.Format("Hi! This contents no is '{0}'.", i),
          Author = new User() { Name = "nanasi san" },
          };
        blogPost.Comments = new List<Comment>();
        blogPost.Comments.Add(new Comment() { Text = "Good job!!" });

        await BlogPostRepository.CreateBlobPostAsync(blogPost);
      }
    }

    /// <summary>
    /// すべてのBlogPostデータを取得します。
    /// </summary>
    /// <returns></returns>
    private async Task GetAllBlogPosts()
    {
      this.blogPosts = await BlogPostRepository.GetAllBlogPostsAsync();
    }
  }
}

mainクラス実装のポイントは以下の通りです。

  • program.InitializeDatabase();
    リポジトリークラスの呼び出しにより、データベース・コレクションを作成しています。

  • program.InitializeData().Wait();
    リポジトリークラスの呼び出しにより、BlogPostサンプルデータを50件作成しています。

  • program.GetAllBlogPosts().Wait();
    リポジトリークラスの呼び出しにより、データベースコレクションに登録された全てのBlogPostデータ抽出しています。

  • ログ出力
    抽出の結果として得られたデータは「IEnumerable<BlogPost>」型です。
    個別データである BlogPost オブジェクトをSerilogでコンソール出力しています。
    Serilogを使用する理由は、オブジェクトのログ出力に際し、子プロパティを含めた形で自動でJSON形式出力してくれる機能を活用する為です。

以下がDocDbExampleCoreConsoleコンソールアプリケーションの実行結果となります。

2016-12-08 02:37:27 [Information] BlogPost { Id: "43eb6293-c532-46dd-969d-e6e4258bcd7d", Title: "Title 0", Contents: "Hi・・This contents no is '0'.", Author: User { Id: null, Name: "nanasi san" }, Comments: [Comment { Id: null, Text: "Good job!!" }] }
2016-12-08 02:37:27 [Information] BlogPost { Id: "86380c10-c45e-46e5-9320-a432be8e838f", Title: "Title 1", Contents: "Hi・・This contents no is '1'.", Author: User { Id: null, Name: "nanasi san" }, Comments: [Comment { Id: null, Text: "Good job!!" }] }
2016-12-08 02:37:27 [Information] BlogPost { Id: "77350d85-6e11-49ac-9f52-d14729cb41b4", Title: "Title 2", Contents: "Hi・・This contents no is '2'.", Author: User { Id: null, Name: "nanasi san" }, Comments: [Comment { Id: null, Text: "Good job!!" }] }
2016-12-08 02:37:27 [Information] BlogPost { Id: "e151fef2-74d8-4735-a89a-971603169c36", Title: "Title 3", Contents: "Hi・・This contents no is '3'.", Author: User { Id: null, Name: "nanasi san" }, Comments: [Comment { Id: null, Text: "Good job!!" }] }
2016-12-08 02:37:27 [Information] BlogPost { Id: "580291de-c99a-4893-b416-48954847413a", Title: "Title 4", Contents: "Hi・・This contents no is '4'.", Author: User { Id: null, Name: "nanasi san" }, Comments: [Comment { Id: null, Text: "Good job!!" }] }
2016-12-08 02:37:27 [Information] BlogPost { Id: "a80815e6-5d03-49e9-b12e-a2892f504dbd", Title: "Title 5", Contents: "Hi・・This contents no is '5'.", Author: User { Id: null, Name: "nanasi san" }, Comments: [Comment { Id: null, Text: "Good job!!" }] }
...以下省略

⑥「Azure DocumentDB Emulator」のExplorerからデータを確認

「Azure DocumentDB Emulator」の管理画面である「https://localhost:8081/_explorer/index.html#」から投入されたBlogPostデータを参照してみます。
以下の画面のようにデータを確認する事が出来ます。

f:id:daigo-knowlbo:20161208030020p:plain

ちなみにタスクトレイアイコン マウス右ボタン「Reset Data...」をクリックすると、簡単にきれいさっぱりデータが消去されるのでお気を付けください。

f:id:daigo-knowlbo:20161208030235p:plain

「Azure DocumentDB」から「Azure DocumentDB Emulator」にデータを(エクスポート)リストア

最後にクラウド上の Azure DocumentDB から Azure DocumentDB Emulator にデータリストアする手順を説明しておきます。

「本来の実装はクラウドのAzure DocumentDBで行っているが、出先等で一時的にローカルエミュレータ開発をしたい」
「今までEmulatorが無かったから、常にクラウドに接続していたけれど、クラウドエミュレータを平行利用開発したい」

等々の場合に有効かと・・・

以下のページから「Azure DocumentDB Data Migration Tool」をダウンロードします。

Download Azure DocumentDB Data Migration Tool from Official Microsoft Download Center

ダウンロードしたzipファイルを任意のフォルダに解凍し dtui.exe を起動します。

f:id:daigo-knowlbo:20161208031143p:plain

「Nextボタン」をクリックします。

表示された以下のウィンドウにて、Import from は「DocumentDB」、ConnectionString は「Azure上のDocumentDBへの接続文字列(Database=xxx も忘れずに)」、Collection は「インポート対象のコレクション名」を入力して「Nextボタン」をクリックします。

f:id:daigo-knowlbo:20161208031904p:plain

以下の画面において、export先(つまり、ここではローカルの Azure DocumentDB Emulator)の設定を行います。
ConnectionString は、EndPoint=「https://localhost:8081」・AccountLey=「固定値」Database=「任意(ここではBlog)」となります。
入力したら「Nextボタン」をクリックします。

f:id:daigo-knowlbo:20161208032231p:plain

以下のウィンドウが表示されたら「Next」ボタンをクリックします(必要であれば「エラーログファイル」の設定も行って)。

f:id:daigo-knowlbo:20161208035350p:plain

設定内容の最終確認を行う以下の画面が表示されいます。
問題なければ「Next」ボタンをクリックします。

f:id:daigo-knowlbo:20161208032714p:plain

以上で「クラウド Azure DocumentDB」→「ローカル Azure DocumentDB Emulator」へのデータのリストアが完了しました。

f:id:daigo-knowlbo:20161208032642p:plain

まとめ

NoSQLが登場したのはいつだったでしょうか・・・RedisやCassandra、memcashedに興奮し、迷走し・・・Azure黎明期にも当時のNoSQL(DoucmentDBという名称ではなかったと思う・・・)に興奮した記憶があります。
ビジネスアプリケーションにおいては、まだまだNoSQLの導入は遅れているように思います。同時にNoSQLがRDBにとって代わる技術では無い事も事実です。
RDBの堅牢なデータ管理とNoSQLのパフォーマンスを適材適所に組み合わせたシステムの開発、という提案を開発者として行っていきたいものです。

で、まあ新しい技術への探求は相変わらず楽しいのですよね^^

Visual Studio for Mac + Visual Studio Team Servicesでソース管理する

仕事レベルのプログラム開発の場合、ソース管理は必ず必要となります。
しかし、今時は個人開発であってもソース管理は必要ですし、また、サンプルの実装をGithubで公開するといった事も日常的な事でしょう。

今回は開発環境として「Visual Studio for Mac」を、ソース管理に「Visual Studio Team Services(以下VSTS)」を使用する手順について説明します(ソース管理はGitです)。

VSTSにプロジェクトを構成する

[Step 1] プロジェクトを作成

ブラウザで自分のVSTSサイトにアクセスします。
ダッシュボード上の「Recent projects & teams」下の「New」をクリックします。

f:id:daigo-knowlbo:20161204153616p:plain

「Create team project」ウィンドウが表示されますので、必要項目を入力し「Create project」ボタンをクリックします。Version ControlはGitにします。

f:id:daigo-knowlbo:20161204153751p:plain

しばらく待つとプロエジェクトの作成が完了します。「Navigate to project」ボタンをクリックしてプロジェクトポータルに移動しましょう。

f:id:daigo-knowlbo:20161204154136p:plain

[Step 2] セキュリティ設定を実施

右上のユーザーアイコンをクリックし、表示されたポップアプメニューから「Security」をクリックします。

f:id:daigo-knowlbo:20161204154228p:plain

Personal access tokenの「Add」ボタンをクリックします。(Personal access tokenの詳細は後述します)

f:id:daigo-knowlbo:20161204154406p:plain

Descriptionを任意で入力します。アクセストークンの有効期限や認可範囲の細かな設定が可能ですが、ここではひとまずデフォルト値の90日間の有効期限、フルスコープ権限とします。画面下部の「Create token」ボタンをクリックします。

f:id:daigo-knowlbo:20161204154756p:plain

f:id:daigo-knowlbo:20161204154819p:plain

トークンが発行されますので、これをコピーして手元に保存しておきます。

f:id:daigo-knowlbo:20161204155124p:plain

[Step 3] GitのURLを取得(確認)

左上のプロジェクト選択UIから今回作成した「VsMacAndVsts」を選択します。

f:id:daigo-knowlbo:20161204155310p:plain

「Code」タブを選択します。

f:id:daigo-knowlbo:20161204155506p:plain

GitのURLをコピーします。

f:id:daigo-knowlbo:20161204155557p:plain

以上で VSTS 側の設定準備は整いました。

Personal access tokenとは

「Personal access token」は Visual Studio for Mac からVSTSへGitアクセスするために必要となるトークンです。
以下のMicrosoft公式サイトに記述があるように「Microsoftアカウント・Azure AD認証によるネイティブ認証に対応していないクライアントからの資格情報認証の為のトークン」となります。

www.visualstudio.com

具体的には、パーソナルアクセストークンはVSTSにアクセスする際の「ユーザー名に対応するパスワード」となります。

Visual Studio for Macプロジェクトの作成

では、作成したVSTSでソース管理を行うプロジェクトをVisual Studio for Macで作成します。

[Step 4] VS for Macプロジェクトを作成

Visual Studio for Macを起動して「New project...」ボタンをクリックします。

f:id:daigo-knowlbo:20161204161054p:plain

ここでは .NET Core Console アプリケーションを作成することとします。

f:id:daigo-knowlbo:20161204161155p:plain

プロジェクト名や保存場所を設定します。ここではプロジェクト名は「HelloVstsConsole」としました。
また、「バージョン コントロールに Git を使用します。」にチェックを付けます。

f:id:daigo-knowlbo:20161204161244p:plain

[Step 5] プロジェクトに対してリモートリポジトリ(VSTS)を設定

プロジェクトが作成できたら、メニュー「バージョン管理→ブランチとリモートを管理する」を選択します。

f:id:daigo-knowlbo:20161204161511p:plain

「Git リポジトリの構成」ウィンドウが表示されるので、「リモート ソース」タブを選択し「追加」ボタンをクリックします。

f:id:daigo-knowlbo:20161204161719p:plain

「リモート ソース」ウィンドウが表示されたら、任意の名前を設定し、「URL」項目にGITリポジトリのURLを設定します。
このURLは上述「VSTSにプロジェクトを構成する」の「Step 3」で取得(確認)したURLになります。

f:id:daigo-knowlbo:20161204161907p:plain

ここまでで、Visual Studio for Macにおけるプロジェクトに対する下準備が完了しました。

[Step 6] ローカルリポジトリへコミット

では、作成したHelloVstsConsoleプロジェクトのGitソース管理へのコミット作業を行いましょう。
メニュー「バージョン管理→ソリューションを確認してコミット」を選択します。

f:id:daigo-knowlbo:20161204163047p:plain

追加されたソースの一覧を表示されますので、「コミット」ボタンをクリックします。

f:id:daigo-knowlbo:20161204163217p:plain

「ファイルをコミットする」ウィンドウが表示されます。
「コミットメッセージ」を入力し、「コミット」ボタンをクリックします。
この操作により、「ローカルリポジトリ」に対するコミットが完了します。(つまり、まだVSTSのソースは更新されていません。)

f:id:daigo-knowlbo:20161204164658p:plain

[Step 7] VSTSへプッシュ

次にVSTSにソースをアップしましょう。
メニュー「バージョン管理→変更をプッシュ」を選択します。

f:id:daigo-knowlbo:20161204164848p:plain

リポジトリへプッシュ」ウィンドウが表示されます。
「プッシュ先」は先程設定した「VsMacAndVsts」を選択し、「変更をプッシュ」ボタンをクリックします。

f:id:daigo-knowlbo:20161204165020p:plain

「Git 資格情報」ウィンドウが表示されます。
「ユーザー名」には、VSTSへのログインIDを設定します。
「パスワード」には、「VSTSにプロジェクトを構成する」のStep 2で得た「パーソナルアクセストークン」を設定します。

f:id:daigo-knowlbo:20161204165212p:plain

以上で「ローカルリポジトリへのコミット」→「VSTSへのプッシュ」が完了しました。

プッシュされたソースを確認する

では、再度VSTSサイトにアクセスし、Visual Studio for Macで作成したプロジェクトコードがアップされたか確認してみましょう。
以下が VsMacAndVsts プロジェクトのCodeの表示です。Visual Studio for Macで作成したプロジェクトを確認することができます。

f:id:daigo-knowlbo:20161204165622p:plain

ソースを変更して再コミット&プッシュ

ではProgram.csを以下のように修正してみます。
(元々は「Console.WriteLine("Hello World!");」でした。)

using System;

class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine("変更したよ");
  }
}

再びローカルリポジトリにコミットします。(メニュー「バージョン管理→ソリューションを確認してコミット」→コミットボタン)

f:id:daigo-knowlbo:20161204170047p:plain

続いてVSTSリモートリポジトリに変更をプッシュします。(メニュー「バージョン管理→変更をプッシュ」)

f:id:daigo-knowlbo:20161204170126p:plain

VSTSサイトを確認すると変更が正しくプッシュされたことを確認することができます。

f:id:daigo-knowlbo:20161204170138p:plain

まとめ

はい。という事で Visual Studio for Mac + Visual Studio Team Services によるソース管理(バージョン管理)の設定手順の説明となりました。
VSS / SVN / TFS / GIT・・・と多種多様なソース管理(バージョン管理)システム、そして、Eclipse / Visual Studio / Visual Studio for Mac / Visual Studio Code / Web Storm 等々の多くの開発環境が乱立しています。
各種技術を学び、適切なツール・適切なソリューションを選択して、幸せな開発生活を送りたいですね。

Entity Framework Core 1.1のHasField()とUsePropertyAccessMode()を使ってみた

Entity Framework Core 1.0 → 1.1における機能追加の1つとして「HasField()メソッドの追加」というものがあります。
Entity Frameworkでは、基本的に
「モデルクラス=データベース上のテーブル」
「モデルクラスのプロパティ=データベーステーブル上のカラム」
というマッピングを行います。
HasField()を利用すると「(クラス)フィールド」をデータベーステーブルカラムにマップすることが出来ます。

テスト環境

本投稿のテスト環境は以下の通りです。

C:\Users\ryuichi>dotnet --info
.NET Command Line Tools (1.0.0-preview4-004110)

Product Information:
 Version:            1.0.0-preview4-004110
 Commit SHA-1 hash:  740a7fe3fd

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.14393
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\1.0.0-preview4-004110

.NET Core Downloadsからダウンロードできるstableなバージョンではなく、デイリービルドされているGithub上のhttps://github.com/dotnet/cliから2016/11/23にダウンロードを行いました。
このバージョンは dotnet new で .csproj を生成します。

データベーステーブル定義

まず、今回使用するデータベーステーブルの定義は以下のようなものを想定します。

f:id:daigo-knowlbo:20161124025924p:plain

テーブル名は UserAccount で、列定義は「主キーである ID、氏名を表す FirstName / LastName、TwitterアカウントIDを表す ValidatedTwitterId」という構成です。ValidatedTwitterIdとは、確かにTwitter上に対象IDが存在すると検証したTwitterIDという意味です。

プロジェクトを作成

コマンドプロンプトを開き、dotnetコマンドによりプロジェクトを作成します。
ここでは、c:\Projects\dotNet\efCore11Exampleフォルダで以下のコマンドを実行しました。

dotnet new

.csprojに参照設定を追加

本サンプルではEntity Framework 1.1 / HttpClient を利用します。
csprojファイルは以下のように PackegeReference を追加しています。

// efCore11Example.csproj
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
  
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="**\*.cs" />
    <EmbeddedResource Include="**\*.resx" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NETCore.App">
      <Version>1.0.1</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.NET.Sdk">
      <Version>1.0.0-alpha-20161104-2</Version>
      <PrivateAssets>All</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.EntityFrameworkCore">
      <Version>1.1.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer">
      <Version>1.1.0</Version>
    </PackageReference>
    <PackageReference Include="System.Net.Http">
      <Version>4.3.0</Version>
    </PackageReference>
  </ItemGroup>
  
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

.csprojを修正した後、「dotnet restore」を実行しておきましょう。

モデルクラスの実装

UserAccountモデルクラスを実装します。

// UserAccount.cs
using System.Net.Http;
using System.Threading.Tasks;

namespace efCore11Example.Models 
{
  public class UserAccount
  {
    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    private string _validatedTwitterId;

    public async Task SetTwitterId(string twitterID)
    {
      using (var client = new HttpClient())
      {
        string url = "https://twitter.com/" + twitterID;
        var response = client.GetAsync(url).Result;
        var stringResponse = await response.Content.ReadAsStringAsync();
        if( !stringResponse.Contains("<form class=\"search-404\" action=\"https://twitter.com/search\" method=\"get\">") )
        {
          this._validatedTwitterId = twitterID;
        }
      }
      
    }
    public string GetTwitterId()
    {
      return this._validatedTwitterId;
    }
  }
}

UserAccountテーブルのカラムに該当する「Id / FirstName / LastName」は、通常通りのプロパティとして実装します。
ValidatedTwitterId列に対応するプロパティは定義せず、代わりに「validatedTwitterId」フィールドを用意します。
また、
validatedTwitterIdフィールド値を設定するための「SetTwitterId()メソッド」を用意します。このメソッドは、twitterサイトにHTTPリクエストを行い、TwitterIDの存在チェックを行っています。あまり良い実装ではありませんが、Twitterに対して存在しないTiwtterIDのページをリクエストした際、
”レスポンスされたHTMLに「class="search-404"」というタグが含まれる”
という事柄を利用してTwitterIDの存在を確認しています。

DbContextクラスの実装

次に、DbContextクラスを継承したExampleDbContextクラスを実装します。

// ExampleDbContext.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace efCore11Example.Models 
{
  public class ExampleDbContext : DbContext
  {
    public DbSet<UserAccount> UserAccounts { get; set; }

    public ExampleDbContext()
    {
    }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
      optionsBuilder.UseSqlServer(@"Server=tcp:rdexampledb2svr.database.windows.net,1433;Initial Catalog=RdExampleDb2;Persist Security Info=False;User ID=[user id];Password=[password];MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;");
    }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
      modelBuilder.Entity<UserAccount>()
        .Property<string>("ValidatedTwitterId")
        .HasField("_validatedTwitterId")
        .UsePropertyAccessMode(PropertyAccessMode.Field);
    }
  }
}

DbSetとしてUserAccountsプロパティを定義します。
OnConfiguring()メソッドで接続先SQL Serverへの接続文字列を設定しました。ここでは私のAzure上のSQL Databaseへの接続文字列の設定を行っています。

そして、OnModelCreating()での実装が今回の肝になります。
UserAccountモデルクラスとデータベーステーブルのマッピングについて追加の情報を設定しています。
「.Property("ValidatedTwitterId")」は、ValidatedTwitterIdという名称のプロパティをモデルクラスに追加することを意味しています。つまり、マップ先のデータベーステーブルのカラム名になります。
「.HasField("validatedTwitterId").UsePropertyAccessMode(PropertyAccessMode.Field);」は、ValidatedTwitterIdプロパティに該当するモデルクラスの要素は「validatedTwitterId」であり、それは「フィールドとしてアクセス可能である」と定義しています。

以上で、モデルクラスおよびDbContextの準備が整いました。

ExampleDbContext / UserAccountを利用してDB操作を行う

main()メソッドに、ExampleDbContextを利用してUserAccountの追加 および 読み取り を行う実装を行うこととします。

// Program.cs
using System;

using efCore11Example.Models;

namespace efCore11Example
{
  class Program
  {
    static void Main(string[] args)
    {
       // レコードを追加
       AddAccountUsers();
       // レコードを読み取り
       ReadAccountUsers();
    }

    // 2件のUserAccountをDBに追加します。
    // 1件は不正なTwitterIDです。
    public static void AddAccountUsers()
    {
      using(ExampleDbContext context = new ExampleDbContext()) 
      {
        UserAccount userAccount = new UserAccount();
        userAccount.Id = 1;
        userAccount.FirstName = "ryuichi";
        userAccount.LastName = "daigo";
        userAccount.SetTwitterId("ryuichi111std").Wait();

        context.UserAccounts.Add(userAccount);

        UserAccount userAccount2 = new UserAccount();
        userAccount2.Id = 2;
        userAccount2.FirstName = "fusei";
        userAccount2.LastName = "account";
        userAccount2.SetTwitterId("fuseinatwitterid").Wait();

        context.UserAccounts.Add(userAccount2);

        context.SaveChanges();
      }
    }

    // UserAccountテーブルから読み取ってコンソール出力を行います。
    public static void ReadAccountUsers()
    {
      using(ExampleDbContext context = new ExampleDbContext()) 
      {
        foreach( var userAccount in context.UserAccounts)
        {
          System.Console.WriteLine("氏名: " + userAccount.FirstName + userAccount.LastName);
          System.Console.WriteLine("TwitterID: " + userAccount.GetTwitterId());
          System.Console.WriteLine("-----");
        }
      }
    }
  }
}

コード内コメントにもあるように2人の UserAccount を追加します。1人は存在するTwitterIdを設定し、もう1人は存在しないTwitterIdを設定しています。後者の 存在しないTiwtterId を指定したユーザーは UserAccount.SetTwitterId() 内の処理により_validatedTwitterIdフィールド には値が反映されません。

実行する

dotnet run」コマンドでプログラムを実行します。
実行結果は、以下の通りです。

C:\Projects\dotNet\efCore11Example>dotnet run
氏名: ryuichidaigo
TwitterID: ryuichi111std
-----
氏名: fuseiaccount
TwitterID:
-----

SQL Management Studioでデータベーステーブルを確認した結果は以下の画面キャプチャになります。

f:id:daigo-knowlbo:20161124033704p:plain

.NET Core開発で project.json / .csproj に手動で参照追加する場合・・・

こんな事ないですか?また、初心者な人、悩む事ないですか?的な記事です。

プロジェクトへの「パッケージ参照(アセンブリ参照)追加」は、Visual Studio などの統合開発環境を使用しているとGUI操作のみで完了してしまいます。
しかし、CLIVisual Studio Codeを使って開発をしている場合、project.json もしくは .csproj ファイルに手動で参照の追加を記述する必要があります。

【補足】project.json と .csproj
2016.11.23現在、.NET Coreではバージョンによってプロジェクトファイルの形式をproject.json形式としているバージョンとMSBuildの.csproj形式としているバージョンが混在しています。今後は .csproj 形式に統一(移行)すると見られます。

利用したいクラスのヘルプ及び名前空間を調べる

例えば「HttpClientクラス」を利用したい、と考えたと想定します。
(ブログ記事などをググって得られたコードスニペットからクラス名と使い方は分かったけれど、名前空間と実装アセンブリが省略されている、といったケースをここでは想定しています。)

HttpClientはその名前からも想像できますが、「HTTPリクエストの送信・HTTPレスポンスの受信」をプログラムで処理する事ができます。

まず、以下のURLに「.NET CoreのAPI reference」が公開されています。

https://docs.microsoft.com/ja-jp/dotnet/core/api/index

f:id:daigo-knowlbo:20161123165802p:plain

左上のテキストボックス(Filterと書かれたテキストボックス)に「HttpClient」と入力します。
続けて、フィルタリング結果の中から「HttpClient」をクリックします。

f:id:daigo-knowlbo:20161123170001p:plain

HttpClientが属する名前空間は以下の赤枠部分になります。
プログラム中では、これをusingしましょう。

f:id:daigo-knowlbo:20161123170449p:plain

同ページの一番下の部分までスクロールしてみます。
「Assembly」という項目があり、ここでは「System.Net.Http.dll」となっています。

f:id:daigo-knowlbo:20161123170551p:plain

つまり、HttpClientクラスは「System.Net.Http名前空間に属するクラスであり、System.Net.Http.dllアセンブリに実装されている」という事が分かりました。

nugetパッケージの調査

では次に、System.Net.Httpのnugetパッケージを調べます。
ブラウザで以下のURLにアクセスします。

https://www.nuget.org/

これは、nugetのトップページになります。
ページ上部の「Search Packages」に「System.Net.Http」と入力しEnterキーをクリックします。
検索結果が表示され、一番上の項目が今回探していたものであると分かります。

f:id:daigo-knowlbo:20161123171147p:plain

対象のページに移動すると、対象のパッケージの最新バージョンのページが表示され、「対象パッケージが依存するフレームワークアセンブリのバージョン」「同パッケージの過去のバージョン履歴一覧」が表示されます。
という事で、今回は2016/11/23現在の最新バージョンである 4.3.0 を利用することにします。

参照(依存)設定の記述(project.json形式の場合)

まず、project.jsonの例は以下になります。project.jsonの場合、dependencies項目への追記となります。

--- project.json ---
...省略
  "dependencies": {
    "System.Net.Http": "4.3.0",
  },
...省略

dependencies配下のキー項目が”パッケージ名”、値項目が”バージョン番号”となります。

参照(依存)設定の記述(.csproj形式の場合)

.csprojの例は以下になります。→配下にタグを追記します。

--- .csproj ---
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
  
  ...省略

  <ItemGroup>

    <PackageReference Include="System.Net.Http">
      <Version>4.3.0</Version>
    </PackageReference>
  </ItemGroup>

  ...省略
</Project>

PackageReference要素のInclude属性が”パッケージ名”、配下のVersionタグ値が”バージョン番号”となります。

バージョンを明記しない

また、参照(依存)の記述の際にバージョン番号部分を「*(ワイルドカード)」とすると、その時点での最新バージョンを参照する事を意味します。
project.json / .csprojの例は以下になります。

--- project.json ---
...省略
  "dependencies": {
    "System.Net.Http": "*",
  },
...省略
--- .csproj ---
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
  
  ...省略

  <ItemGroup>

    <PackageReference Include="System.Net.Http">
      <Version>*</Version>
    </PackageReference>
  </ItemGroup>

  ...省略
</Project>

プログラムを書く

project.json / .csprojを整えた上で dotnet restore する事で、以下の様な HttpClient を利用したプログラムのビルドが通る様になります。

using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        HttpTest().Wait();;

        Console.WriteLine("Hello World!");
    }

    static async Task HttpTest()
    {
        using( HttpClient httpClient = new HttpClient() )
        {
            var response = await httpClient.GetAsync("http://www.microsoft.com/");
            var stringResponse = await response.Content.ReadAsStringAsync();

            Console.WriteLine(stringResponse);
        }
    }
}

おまけ - Entity Framework Core のAPI Referenceはこちらです

Entity Framework Core のAPI Referenceは以下になります。

Entity Framework Core API Reference | Microsoft Docs

会社のブログができたので・・そっちにもたまに投稿します

私の所属する会社の「開発者ブログ」なるものが立ち上がりました。
ということで、そっちにもたまに投稿します!

dev.knowlbo.co.jp

という宣伝投稿でした。

本ブログは .NET Core を軸に、会社のブログへの投稿は Xamarin などのモバイル系および業務的に得たTips的なものを投稿していこうかと思っています。

ひとまず、本日は”「Visual Studio for Mac」でXamarin Forms + ASP.NET Coreシステムを作る”という投稿を行ってみました。

dev.knowlbo.co.jp

ではよろしければ覗いてくださいー

ASP.NET CoreでAutoMapperを使う

AutoMapperも既に.Net Coreへの対応が行われております。
ということで、ASP.NET CoreでAutoMapperを動かしてみたいと思います。

テスト環境

テスト環境はMacで、dotnet --info の結果は以下の通りです。

ryuichi:coreMvcAutoMapper daigo$ dotnet --info
.NET Command Line Tools (1.0.0-preview2-1-003177)

Product Information:
 Version:            1.0.0-preview2-1-003177
 Commit SHA-1 hash:  a2df9c2576

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  10.12
 OS Platform: Darwin
 RID:         osx.10.12-x64

そもそもAutoMapperとは

一応、おさらい。
その名前の通り「自動でマップしてくれる人」です。
プログラム的な表現としては、「異なるオブジェク間のマッピングを行ってくれるライブラリ」です。
オブジェクトのマッピングは、いつ必要になるか?それは レイヤー間でオブジェクトを受け渡す際です。レイヤー間とは「同一プロセス内のクラスメソッド間」の場合もあれば、「プロセスをまたぐ」場合、「ネットワークをまたぐ」場合もあります。

1つの例として、「SPA(Single Page Application)において、WebAPI呼び出しで画面表示データを取得するケース」を考えます。
サーバーサイドの処理としては「オブジェクトのリレーション関係がドメイン領域を適切に表す構造」であることが望まれるかもしれませんが、ブラウザクライアントのUIにおいては「画面表示に必要な項目のみを含んだフラットなデータ構造」が望まれる場合があります。

つまり、改めて言い換えると、ビジネスロジックにおいては、問題領域を扱う為のドメインクラスの形でモデルクラスを保持する事が適切な場合でも、それを画面に表示するView領域では、ドメインクラスを画面表示に最適化したシンプルなDTOである方が適切なケースがあります。

AutoMapperを使う

では具体的なAutoMapperの使い方の説明に入ります。
ここでは、ASP.NET Core MVCの基本プロジェクトが構成されている事を前提とします。

Model / DTOの準備

サンプルとして書籍販売サイトで、販売中の書籍一覧を表示するようなケースを想定します。
以下のような構造の「Bookクラス」を「BookDtoクラス」にマップすることを想定します。

f:id:daigo-knowlbo:20161117231151p:plain

Bookクラスはメンバープロパティとして「Authorオブジェクト」「Publisher」オブジェクトを保持します。
それに対してBookDtoクラスは画面表示に必要な項目をフラットに保持しています。

具体的な実装は以下の通りです。

// Book.cs
namespace CoreMvcAutoMapper.Models
{
  public class Book {
    public int Id { get; set; }
    public string Name { get; set; }
    public string ISBN { get; set; }
    public Author Author { get; set;}
    public Publisher Publisher { get; set; }
  }
}
// Author.cs
namespace CoreMvcAutoMapper.Models
{
  public class Author {
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
  }
}
// Publisher.cs
namespace CoreMvcAutoMapper.Models
{
  public class Publisher {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string Phone { get; set; }
  }
}
// BookDto.cs
namespace CoreMvcAutoMapper.Models.Dto
{
  public class BookDto {
    public int BookId { get; set; }
    public string BookName { get; set; }
    public string ISBN { get; set; }
    public string AuthorName { get; set; }
    public string PublisherName { get; set; }
  }
}

Dependenciesの追記

まず Project.json に対して AutoMapper 関連のライブラリ参照を定義します。
以下が Project.json の抜粋ですが、 "AutoMapper" / "AutoMapper.Extensions.Microsoft.DependencyInjection" の2つライブラリへの参照を追加しています。

{
  ...省略...

  "dependencies": {
  "Microsoft.NETCore.App": {
    "version": "1.1.0",
    "type": "platform"
  },
  "Microsoft.AspNetCore.Mvc": "1.0.1",
  "Microsoft.AspNetCore.Razor.Tools": {
    "version": "1.0.0-preview2-final",
    "type": "build"
  },
  "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
  "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",

  "AutoMapper": "5.1.1",
  "AutoMapper.Extensions.Microsoft.DependencyInjection": "1.1.2"

  },

  ...省略...
}

Profileの準備

Profileクラスを用意します。
Profileクラスでは、オブジェクト間のマッピング方法の定義を行います。
今回の例では Bookクラス を BookDtoクラス にどのようにマップするかという定義を行います。
以下が実装になります。

// AutoMapperProfileConfiguration.cs
using AutoMapper;

using CoreMvcAutoMapper.Models;
using CoreMvcAutoMapper.Models.Dto;

namespace CoreMvcAutoMapper
{
  public class AutoMapperProfileConfiguration : Profile
  {
    public AutoMapperProfileConfiguration() 
    {
      CreateMap<Book, BookDto>()
        .ForMember( dst => dst.BookId, src => src.MapFrom(s => s.Id))
        .ForMember( dst => dst.BookName, src => src.MapFrom(s => s.Name))
        .ForMember( dst => dst.AuthorName, src => src.MapFrom(s => s.Author.LastName + s.Author.FirstName));
    }
  }
}

定義するプロファイルクラスは、AutoMapper.Profileクラスを継承します。AutoMapperProfileConfigurationというクラス名は任意の名前でOKです。
コンストラクタ内で「CreateMap<T1, T2>()メソッド」を呼び出しています。これによって「クラス間のマッピング定義」をAutoMapperに対して宣言することになります。
T1が変換元クラス名、T2が変換先クラス名、となります。
ForMember()定義が3つ並んでします。それぞれ、「変換先オブエジェクトの対象プロパティに対して、変換元のどのプロパティを割り当てるか」を宣言しています。
つまり、「Book.Id は BookDto.BookId にマップ / Book.Name は BookDto.BookName にマップ」 となります。
3つ目のForMember()は、Book.Authorオブジェクトでは「FirstName」と「LastName」に分かれていた著者名を連結して、BookDto.AuthorNameプロパティにマップすることを定義しています。
さらにもう1つ「BookDtoクラスには ISBN プロパティ」が存在しますが、これについて ForMember() 定義がなされていません。しかし、この後のプログラムを実行すると、きちんと「Book.ISBN → BookDto.ISBN」のマップが行われます。これは「変換元と変換先でプロパティ名が同一の場合、自動的にマップが行われる」というAutoMapperの仕様が働くためです。

Mapperの構成初期化とDI登録

Profileはマップの定義であり、具体的なマップ処理を実行(プログラムからキック)するのは「IMapperインターフェイス」オブジェクト経由での操作になります。
今回はASP.NET Core MVCから AutoMapper を利用する為、APS.NET Core MVC の DI(Dependency Injection) の仕組みに従って「IMapperオブジェクトをコントローラに対して、コンストラクタ インジェクション」する事とします。
この初期化処理は Startup.cs で行います。

// Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

using AutoMapper;

namespace CoreMvcAutoMapper
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
        }
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            // AutoMapperをプロファイルを元に初期化してサービスに登録
            services.AddAutoMapper(cfg =>{
                cfg.AddProfile<AutoMapperProfileConfiguration>();
            });
            // IMapper型に対してMapperオブジェクトをsingletonでDIする様に定義を追加
            services.AddSingleton<IMapper, Mapper>();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

上記は最低限のStartup実装としています。通常の ASP.NET Core MVC プロジェクトではロガーの初期化、構成ファイルの読み取り、DIへの各種サービスの追加等々、各処理が追記されるはずです。
ポイントは ConfigureServices(IServiceCollection services) において以下の2つの処理を行うことにあります。

  • 「services.AddAutoMapper()」:Profileを元にAutoMapperの初期化とサービスへの追加を行う。
  • 「services.AddSingleton()」:IMapper型オブジェクトが求められた際に Mapperオブジェクトをインジェクションする様にDIサービスに追加する。

Map処理で型コンバート

「Book→BookDto変換を実装したMapperオブジェクト」を、コントローラクラスでコンストラクタインジェクションにより受け取り、利用したいと思います。
以下は BookControllerクラス の定義であり、「List()メソッド」はBookDtoリストをJSON形式で返却するWebAPIの実装になります。

// BookController.cs
using System.Collections.Generic;

using Microsoft.AspNetCore.Mvc;

using AutoMapper;

using CoreMvcAutoMapper.Models;
using CoreMvcAutoMapper.Models.Dto;

namespace CoreMvcAutoMapper.Controllers
{
  public class BookController : Controller
  {
    private readonly IMapper _mapper;
        
    public BookController(IMapper mapper) { // ← Mapperオブジェクトがインジェクションされる
      // 自分のプロパティに保持
      this._mapper = mapper;
    }

    /*
     * 書籍一覧をJSON形式で取得するWebAPIです。
     */
    public IActionResult List()
    {
      // サンプルのBookオブジェクトリストを取得(実際にはサービス層やDAO層経由で取得)
      List<Book> books = CreateBooks();
      // マップ処理実行!
      List<BookDto> bookDtos = this._mapper.Map<List<Book>, List<BookDto>>(books);
      
      return Ok(bookDtos);
    }

    /*
     * サンプルデータを作成します。
     */
    private List<Book> CreateBooks()
    {
      string[] names = {"C#入門", "Xamarin Form活用", "Visual Studio 2017活用術", "Essential .NET Core", "Java詳説", "実践Docker", "UWP入門", "Professional Azure", "Swift開発", "OSSの世界"};
      string[] publisherNames = {"技術評論社", "翔泳社", "光和コンピュータ"};

      List<Book> books = new List<Book>();

      for(int i=0 ; i<10 ; i++ )
      {
        Book book = new Book() {
          Id = i,
          Name = names[i],
          ISBN = "ISBN-" + i.ToString(),
          Author = new Author() {
            Id = i*10,
            FirstName = "FirstName " + i.ToString(),
            LastName = "LastName " + i.ToString()
          },
          Publisher = new Publisher() { 
            Id = i * 15,
            Name = publisherNames[i%3],
            Address = "Address " + i.ToString() + " - " + i.ToString(),
            Phone = "0120-" + i.ToString() + "-2351"
          }
        };
        books.Add(book);
      }

      return books;
    }
  }
}

コンストラクタの引数としてIMapper型オブジェクトが定義されています。ASP.NET Core MVCランタイム環境はStartupで定義されたサービスへのDIオブジェクト登録に従って IMapper 型に合致するオブジェクト(この場合は、Book→BookDto変換を備えたMapperオブジェクト)を自動的にインジェクションします。 コンストラクタ引数で得られた Mapperオブジェクトは、別のメソッドで利用したい為、privateフィールドメンバー「IMapper _mapper」に保持します。
「List()メソッド」は 、URL「/Book/List」にルーティングされるWebAPIインターフェイスです。
まず「CreateBooks()」で、Bookオブジェクトのリストを取得します。これはサンプルとして固定のBookリストを作成するテスト用メソッドとして用意したものです。
次に以下のMasp()メソッド呼び出しをすることで List → List の変換を行なっています。

List<BookDto> bookDtos = this._mapper.Map<List<Book>, List<BookDto>>(books);

ここではList → Listの変換を行いましたが、単一オブジェクトの変換の場合は以下の様な実装となります。

BookDto bookDto = this._mapper.Map(Book, BookDto>(book);

/Book/List で得られるJSONデータ

上記実装を行なったASP.NET Core MVCアプリケーションを dotnet restore → dotnet run して /Book/List にアクセスしますと、以下のJSONデータが得られます。
各プロパティが正しく変換されたことを確認できます。

[
    {
        "bookId": 0,
        "bookName": "C#入門",
        "isbn": "ISBN-0",
        "authorName": "LastName 0FirstName 0",
        "publisherName": "技術評論社"
    },
    {
        "bookId": 1,
        "bookName": "Xamarin Form活用",
        "isbn": "ISBN-1",
        "authorName": "LastName 1FirstName 1",
        "publisherName": "翔泳社"
    },
    {
        "bookId": 2,
        "bookName": "Visual Studio 2017活用術",
        "isbn": "ISBN-2",
        "authorName": "LastName 2FirstName 2",
        "publisherName": "光和コンピュータ"
    },
    {
        "bookId": 3,
        "bookName": "Essential .NET Core",
        "isbn": "ISBN-3",
        "authorName": "LastName 3FirstName 3",
        "publisherName": "技術評論社"
    },
    {
        "bookId": 4,
        "bookName": "Java詳説",
        "isbn": "ISBN-4",
        "authorName": "LastName 4FirstName 4",
        "publisherName": "翔泳社"
    },
    {
        "bookId": 5,
        "bookName": "実践Docker",
        "isbn": "ISBN-5",
        "authorName": "LastName 5FirstName 5",
        "publisherName": "光和コンピュータ"
    },
    {
        "bookId": 6,
        "bookName": "UWP入門",
        "isbn": "ISBN-6",
        "authorName": "LastName 6FirstName 6",
        "publisherName": "技術評論社"
    },
    {
        "bookId": 7,
        "bookName": "Professional Azure",
        "isbn": "ISBN-7",
        "authorName": "LastName 7FirstName 7",
        "publisherName": "翔泳社"
    },
    {
        "bookId": 8,
        "bookName": "Swift開発",
        "isbn": "ISBN-8",
        "authorName": "LastName 8FirstName 8",
        "publisherName": "光和コンピュータ"
    },
    {
        "bookId": 9,
        "bookName": "OSSの世界",
        "isbn": "ISBN-9",
        "authorName": "LastName 9FirstName 9",
        "publisherName": "技術評論社"
    }
]

AutoMapper参考リンク

「本家」 automapper.org

「本家GitHubgithub.com

「Jimmy BogardのBlog 」
Jimmy Bogard's Blog | Strong opinions, weakly held

「Connect(); 2016」 day1を見た

「Connect(); 2016」を見終えまして、感想を。

去年に引き続き「Connect(); 2016」はDeveloperにとって非常に面白い、興味深い発表やデモが行われたのではないかと思います。
その中で、個人的に印象に残った事柄を以下にポロポロと書き記します。

Visual Studio For Mac

個人的な興味としては「Visual Studio Form Mac」に注目していました。
イベント開催前の、11/16日昼間に Visual Studio For Mac の発表について知ったのですが、一瞬「Visual Studio」の名称からWindows版と同等の物がMacに?と思い・・・いや.NET FrameworkがフルセットはMonoでしか動作しないよな・・・という事は.NET Coreか・・・そして.Net Standard 2.0での FullSet.NET & Core & Xamarin のライブラリ統合か・・・と頭をめぐりました。
そしてConnectでの発表・・「Xamarin Studio + .NET Core + Azure Support」的な=「Visual Studio For Mac」といった印象です。
最近MacではVisual Studio Codeを愛用しておりましたが、ゆくゆくはVisual Studio For Macに移行かな?的な期待も感じました。と同時にVisual Studio Codeは、これはこれで気に入っているのです。高機能であるがファットになり過ぎたWindows Visual Studioに比べてスッキリ・テキパキ、そしてアドイン周りもOSSをより感じられるところが良いのです。

MicrosoftMicrosoftプロダクトを使わない魅力

完全に狙ってだと思いますが、初めの1時間くらい(?)、それ以上(?)、の間、テクノロジーの紹介にMicrosoftプロダクトが一切登場しない、Mac / Linux / GitHub / Docker・・・デモに使用するブラウザもChrome。逆に狙いすぎてる感はありますが、以前の ASP.NET WebForms に代表されるMicrosoft独自オレオレ文化が消え去った事には非常に好感を持てました。そして、この思想にも繋がる、「Any Developer / Any App / Any Platform」がフェアーな思想なのだと思います。
良いものは良いし、悪いものは改善するし、楽しいものを楽しもう!と思います。

Visual Studio 2017 RC

Visual Studio 2017 がRCになりましたね!
まず、「来年出るんだぁ!」。そして分かりやすい良い名前だ!、と。
そう、従来のコードネーム「Visual Studio 15」は、MS開発マニア以外の多くの人から誤解を受けやすいコードネームだったと感じていたのです。VS 2015 と混同して、VS15が次期VSだと認識していないと見受けられるディベロッパーを目にしてきたので・・・。
私はもう眠いので、インストールはまた明日・・・

Linux Foundation / .NET Foundation

MicrosoftLinux Foundation に参加。そして、Google .NET Foundation に参加、とのこと。これは非常に良いことですね!
もう「俺はMS技術者だ」とか「私はJavaの人間だ」とか「Windowsは分かるけど、Linuxは分からない」とか、そんなのは時代遅れなのです!

.NET Core 1.1 / ASP.NET Core 1.1 / Entity Framework Core 1.1リリース

.NET Coreマニアとしてはうれしい情報。でも、まあ、以前からガンガンアップデートしているので Connect での発表で特別うれしいという事ではないけど、まあ心躍る発表でした。
ちなみにインストールしたのだけれど・・・

f:id:daigo-knowlbo:20161117034407p:plain

dotnet --info」しても 1.0.0-preview2-1-003155 と表示されているのはなぜ?3177になってない;;明日調べる・・・

f:id:daigo-knowlbo:20161117034441p:plain

Visual Studio と Docker の蜜月

Dockerとの親和性強化は良いですね。
ただ厳密に、DockerとWindows Server 2016のコンテナとかの技術的相違を把握しきれていなので勉強せねば・・・です。

結果・・「Connect(); 2016」jは毎年Developerにとって非常に心躍る発表や演出を含めた楽しいイベントですね!

★★Visual Studio for Mac★★

www.visualstudio.com

★★Visual Studio 2017 RC★★

www.visualstudio.com