きよくらの備忘録

「三日坊主と呼ばせない!日記」改め。主にソフトウェア開発関連の話題。

knockoutとMappingプラグインとlocalStorageは相性がいいかも

knockout.jsとそのプラグインMapping(knockout.mapping)の組み合わせはすごく便利ですね。このあたりの詳しい話はしばやん先生のこちらのエントリを参照。 Knockout.js の Mapping プラグインを使ったら凄く捗った件 - しばやん雑記

 

 

localStorageとも相性バッチリ

つまるところ、knockout.mappingは以下の二者の間で相互変換をサクッと行ってくれます

  • プレーンなJavaScriptのオブジェクト(及びそのJSONな文字列)
  • (バインド可能なようにObservableなメンバで構成された)ViewModel

JSONとの相互変換も楽なので、RESTなWEB APIとのやり取りをスムーズにこなしてくれるのはもちろん、例えばフォームの内容をlocalStorageに出し入れする時などもすごく楽にできました。

例えばこんなフォームがあるとして:

<dl>
  <dt>name</dt>
  <dd>
    <input type="text" id="name" data-bind="value:name" />
  </dd>

  <dt>age</dt>
  <dd>
    <select id="age" data-bind="value:age">
      <option value="17">17</option>
      <option value="18">18</option>
      <option value="19">19</option>
      <option value="20">20</option>
    </select>
  </dd>

  <dt>email</dt>
  <dd><input type="text" id="name" data-bind="value:email" /></dd>
</dl>

こんな感じでknockoutを使ってフォームとバインドしてやります:

var initData = {
    name: "foo",
    age: 20,
    email: "foo@example.com"
  };

// mapping JavaScript object to ViewModel by knockout.mapping.
var viewModel = ko.mapping.fromJS(initData);

// Bind ViewModel to input form by knockout.js.
ko.applyBindings(viewModel);

Mappingプラグインを使ったいるのが var viewModel = ko.mapping.fromJS(initData); の部分で、オブジェクトからObservableなモデルを自動作成してくれます。素敵。

 

フォームの入力内容をlocalStorageに保存する

ここで、入力された内容をlocalStorageに一時保存したいとしましょう。必要なコードはたったこれだけです:

var formData = ko.mapping.toJSON(viewModel)
localStorage.setItem("formData", formData);

フォームにバインドされているviewModelからはフォームの入力内容が反映された値が取得可能です。これをMappingの機能を使ってJSONの形で受け、そのままlocalStorageにセットします。 一旦変数で受けてますが、1行でも良いですね。もちろんフォーム部品が沢山あっても必要な行数は変わらないでしょう。

 

localStorageに保存された内容を読みだしてフォームに反映する

ではlocalStorageから読み出すのはどうかというと、こちらはこうなります:

var json = localStorage.getItem("formData");
ko.mapping.fromJSON(json, {}, viewModel);

今度はlocalStorageから読み出したJSONを使って既存のマップ済みのViewModelを更新します。これも1行で書けますね。フォーム部品が沢山あっても(以下同文)。

 

 

まとめ

今回はlocalStorageを使いたい事情があったのでlocalStorageを例にしていますが、REST Web APIなどでも普通に利用できると思います(というかそもそもそういう目的で作られたものだと思います)。

knockout自体が軽量コンパクトなこともあり、『ページの一部だけをちょっと双方向バインドしたい』的な用途ではAngularJSよりも使い勝手が良いと思ってたりするのですが、Mappingプラグインがそれをさらに後押ししてくれるように思います。

 

フルバージョンのソースとデモサイト

フルバージョンのソースコードgithubに置いてあります。また、そのまま動くGithubページも置いてみました。

AutoMapperを使ってオブジェクトを詰め替える

本日、ASP.NET MVC Do's and Dont's / Best Practices というまとめ記事が上がっていました*1

その中の一つ(少し古い記事ですが内容は大変に有用だと思います)の12 ASP.NET MVC Best Practicesを見ていたら、

DomainModelからViewModelに詰め替える時には退屈だしエラー積み込むかもしれんからバカ正直にコード書かずにAutoMapperとか使おうぜ(超訳)

とか書いてあるのを見つけました。

AutoMapper、そういえば以前どこかで割と普通なお方がお話されてた資料を見た記憶があるなぁ、と思いだしつつ*2、試してみることにしました。

AutoMapperとは?

AutoMapperとは、その名の通り(?)、オブジェクト同士を自動でマッピングしてくれるライブラリです。 以前はCodePlexで開発されていましたが、現在はGitHubがアクティブなリポジトリとなっているようです。

ざっくり概要を列挙するとこんなところでしょうか?

  • オブジェクトからオブジェクトにデータをコピーして詰め替える
  • コピーする際、プロパティ名の一致等の規約に沿って、自動でマップを行う
  • マッピングの規則はカスタマイズ可能
  • コレクションからコレクションへのマッピングも可能
  • NuGetで簡単に導入可能

さっそく、試してみましょう。 今回はシンプルに、コンソールアプリケーションのプロジェクトを使ってサンプルを作ってみます。

(以下のサンプルの完全なソリューション一色はgithubに置いてあります: kiyokura/AutoMapperSample · GitHub

 

 

AutoMapperの導入

AutoMapperの導入は、先に述べたとおりNuGetで一発で導入可能です。 パッケージ マネージャー コンソールから以下のようにインストールするか、GUIからAutoMapperで検索してインストールします。

PM> Install-Package AutoMapper

これだけで終了です。簡単ですね。

AutoMapperの基本

AutoMapperの基本的な使い方は次の2ステップのようです。

1. マップを作成

まず、Mapper.CreateMap<TSource, TDestination>()メソッドを使ってマップを作成する

2. マップを実行

Mapper.Map<TDestination>(object source)メソッドを使ってマップを実行

 

シンプルですね。 いくつか、実際のサンプルを動かしてみたいと思います。

 

 

サンプル1 : メンバーがまったく同じオブジェクトのマッピング

まずは一対一でオブジェクトをコピーするケースです

こんな二つのクラスがあるとして……

public class User
{
  public int ID { get; set; }
  public string Name { get; set; }
  public int Age { get; set; }
}

public class Member
{
  public int ID { get; set; }
  public string Name { get; set; }
  public int Age { get; set; }
}

基本に忠実に、こんな感じでサクッと。

// DBからとってきた(という体裁の)Memberクラスを、
// 全く同じメンバを持つ、User にマッピングする例

// Mapperを作成
Mapper.CreateMap<Member, User>();

// DBか何かからMember型でデータをとってきたと思ってください
Member member = new Member()
{
  ID = 15,
  Name = "Narami Kiyokura",
  Age = 20
};

//  Member型のmemberをUser型にマップ
User user = Mapper.Map<User>(member);


Console.WriteLine("ID:{0}", user.ID);
Console.WriteLine("Name:{0}", user.Name);
Console.WriteLine("Age:{0}", user.Age);

Console.ReadKey();

 

 

サンプル2 : コピー元とコピー先でメンバが異なる場合

とはいえ、コピー元と先でメンバが完全一致しないケースや、なんらかの加工が必要なケースもあると思います。以下程度の場合は、ごく簡単な加工追加処理だけで対応可能です。

こんな二つのクラスをマッピングしてみます。

public class Student
{
  public int ID { get; set; }
  public string Name { get; set; }
  public string Class { get; set; }
  public string Club { get; set; }
}


public class People
{
  public int PeopleID { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string Class { get; set; }
  public int PrefCode { get; set; }
}

お互いに存在しないメンバーについては、そのまま何もしなくてもエラーにならず、無視されます。 その他の場合は、マップ時にForMember()メソッドを使ってマップを定義してあげることができます。

// DBからとってきた(という体裁の)Peopleクラスを、
// 一部異なる名前のフィールドを持つStudent にマッピングする例

// Mapperを作成
// .ForMemberメソッドで移行先の何をセットするか指定する
// Student.ID に PeopleID をマップ
// Student.Name に People.FirstName とPeople.LastName を組み立てた値をマップ
// Student.Class にはClassはマップしない
// 
// お互いに存在しないものは何もしなくても無視する(People.PrefCodeやStudent.Club)
Mapper.CreateMap<People, Student>()
  .ForMember(d => d.ID, o => o.MapFrom(s => s.PeopleID))
  .ForMember(d => d.Name, o => o.MapFrom(s => s.FirstName + " " + s.LastName))
  .ForMember(d => d.Class, o => o.Ignore());

// DBか何かからPeople型でデータをとってきたと思ってください
People people = new People()
{
  PeopleID = 15,
  FirstName = "Narami",
  LastName = "Kiyokura",
  Class = "これはマップしない",
  PrefCode = 33
};

//  People型のpeopleをStudent型にマップ
Student student = Mapper.Map<Student>(people);


Console.WriteLine("ID:{0}", student.ID);
Console.WriteLine("Name:{0}", student.Name);
Console.WriteLine("Class:{0}", student.Class);
Console.WriteLine("Club:{0}", student.Club);

Console.ReadKey();

もっと複雑になる場合も対応可能ですが、今回は割愛します。 (公式ドキュメントに詳しくいろいろあります)

 

 

サンプル3 : コレクションからコレクションへのマッピング

最後に、コレクションからコレクションへのマップを試してみたいと思います。これも利用する頻度は高くなりそうです。

サンプル1で利用したMemberUserを利用します。

// DBからとってきた(という体裁の)Memberクラスのコレクションを、
// 全く同じメンバを持つ、Userのコレクション にマッピングする例

// Mapperを作成
// ここで指定するのはMember型とUser型
Mapper.CreateMap<Member, User>();

// DBか何かからMember型のコレクションでデータをとってきたと思ってください
List<Member> members = new List<Member>(){
    new Member()
    {
      ID = 15,
      Name = "Narami Kiyokura",
      Age = 20
    },
                        new Member()
    {
      ID = 17,
      Name = "Taro Yamada",
      Age = 25
    }
  };

//  Member型のmemberをUser型にマップ
List<User> users = Mapper.Map<List<User>>(members);

foreach (var user in users)
{
  Console.WriteLine("ID:{0}", user.ID);
  Console.WriteLine("Name:{0}", user.Name);
  Console.WriteLine("Age:{0}", user.Age);
}


Console.ReadKey();

ポイントは以下でしょうか - Mapの作成時はクラス(コレクションではない)を指定 - Mapper.Map<T>()の型引数にコレクション型を指定

これも結構シンプルにできますね。

 

 

まとめ

ということで、AutoMapperをざっくり触ってみました。超便利ですね。

他にも、Flatingといって、ネストされたオブジェクトに対して命名即に沿った形で自動的にフラットなオブジェクトにマッピングする機能も持っていたりします。

AutoMapperについて少し検索してみると、5年近く前にMVP for ASP.NET/IIS の青木さんが紹介記事『AutoMapperでオブジェクト間のデータコピーを行う』を執筆されておられたことを知りました。

青木さんの記事では、私が上記で試していないパターンやASP.NET MVCで実用的に使う場合のマッピング定義の扱い方などについても書かれておられます。AutoMapperに興味を持たれた方は是非、目を通されることをお勧めしたいと思います。

 

 

参考

*1:ちなみにこのサイトはマイクロソフトがホストしているキュレーションサイト『Curah!』(読み:キュラー)という、いわゆるまとめサイト的な奴 : http://curah.microsoft.com/

*2:割と普通氏のスライド資料はこちら:http://www.slideshare.net/normalian/clrh-69

Visual Studio 2013 Update 3 の ASP.NET 周りの変更まとめ

Visual Studio 2013 Update 3 がRTWしました。

f:id:kiyokura:20140805164647p:plain

ダウンロードはこちらあたりから。

 

オフィシャルなアナウンスとしては下記あたりになると思います。

 

ASP.NET及びWEB開発関連の更新

せっかくなので(?)、前述の Announcing new Web Features in Visual Studio 2013 Update 3 RTM の内容に沿って、Web周りの変更を確認してみます。

Microsoft ASP.NET and Web Tools 2013.3 の新機能

  • ASP.NET 5.2 technologies (MVC, Web API) のスキャフォールディングサポートの追加
    • スキャフォールディング時にプロジェクトで利用しているフレームワークのバージョンを正しく検出するので、5.1.2を利用している場合は、5.2ではなく5.1.2としてスキャフォールディングする
  • JSON エディタの改善
    • オートフォーマットをoffに出来るようになった
      • 今までオートフォーマットは自動的に有効になっておりoffにすることは出来なかったが、設定でoffに出来るように
        • f:id:kiyokura:20140805163452p:plain
      • オートフォーマットをoffに設定しても、手動でのフォーマット(コンテキストメニュー or ショートカット)は有効
    • ブレース(波カッコ、{})とブラケット(角カッコ、[])のマッチング
      • 対応するカッコ同士をハイライト表示するようになった
      • f:id:kiyokura:20140805163910p:plain
  • CSS エディタの改善
    • インテリセンスのSelectors Level 4の対応の改善
      • インテリセンスが策定中の Selectors Level 4 のより多くのパターンに対応
      • 例::mutches():not()の()内でもインテリセンスが効く
      • f:id:kiyokura:20140805163938p:plain
    • フォント及び画像ファイルの.cssファイルへのドラッグ&ドロップの対応
      • ソリューションエクスプローラからフォント(eot/svg/ttf/woffの各形式)や画像ファイルをドラッグ&ドロップすると、適切なcssの記述を自動で挿入する
  • MVCおよびWeb Forms (One ASP.NET )にて二要素認証のスケルトン実装の追加
    • One ASP.NET テンプレートのMVCおよびWeb Formsを選択した際のサンプル実装に、ASP.NET Identityの二要素認証のサンプル実装が追加
      • ManageController.csManageViewModels.cs等が追加され、その他のファイルにもコードが追加されている
      • f:id:kiyokura:20140805164006p:plain
  • facebookアプリケーションテンプレートの組み込みの廃止(Visual Studio Gallerへ移動)
    • One ASP.NET テンプレート内からfacebookアプリケーションのテンプレートが無くなりました
      • 廃止されたわけではなく、独立したVS拡張としてVisual Studio Galleryに移動しています
      • facebook側のAPIの変更などに柔軟に対応するための措置と思われます
    • 注意:2014.08.05現在、まだfacebookアプリテンプレートは公開されていない?
  • Enabled creation of ASP.Net projects using AAD when signing in with Microsoft account
    • Azure ADで認証を行うアプリケーションを作成する際、プロジェクト作成時にマイクロソフトアカウントでサイン・インしてプロビジョニングが行えるようになった
      • ……とあるのですが、試してみてもうまいこと行きませんでした……。
      • 私のポータルでのAzure ADの表示あたりからしてスクリーンショットと異なるので、なにか状態が違うのかも?
  • Microsoft Azure WebJobsへのパブリッシュ機能の追加
    • Visual Studioでコンソールアプリケーションを作成して、WebJobsとして発行できるようになった模様
    • f:id:kiyokura:20140805162232p:plain
    • f:id:kiyokura:20140805162237p:plain

フレームワークのアップデート

  • テンプレートで標準で展開されるテンプレートがアップデートされています

その他関連ツールのアップデート

  • WebDeploy 3.5 refresh
  • IIS Express 8.0 June 2014 Hotfix

既知の問題

ASP.NET MVC : 組み込みのヘルパを使わずにバリデーションのメッセージを取得する

確認環境:ASP.NET MVC 5.1(VS2013)

ビューに対して何らかの検証エラーを返した際、当然ですがビューではその内容を表示してあげることになると思います。

スキャフォールディングで作成したビュー等で利用されている通り、組み込みのHTMLヘルパを利用して簡単に表示することもできます。また自分でHTMLを一から組み立てて出力したり、オリジナルのヘルパを作成することも可能です。

 

組み込みのHTMLヘルパを使う場合

ASP.NET MVCでは、バリデーションの結果を表示するために、以下のヘルパが用意されています。

  • Html.ValidationSummary
  • Html.ValidationMessage
  • Html.ValidationMessageFor<>

 

例えば、コントローラーから以下のようにエラーを設定したとします。

public class HomeController : Controller
{
    public ActionResult Index()
    {
        ModelState.AddModelError("foo", "error for foo");
        ModelState.AddModelError("bar", "error for bar");
        ModelState.AddModelError("hoge", "error for hoge");
        return View();
    }
}

 

ビューにはヘルパを使って下記のように書いてみます。

<fieldset>
    <legend>Html.ValidationSummary</legend>
    @Html.ValidationSummary()
</fieldset>

<fieldset>
    <legend>Html.ValidationMessage</legend>
    @Html.ValidationMessage("foo")<br />
    @Html.ValidationMessage("bar")<br />
</fieldset>

 

上記の場合、実行結果のHTMLは以下のようになります*1

<fieldset>
    <legend>Html.ValidationSummary</legend>
    <div class="validation-summary-errors" data-valmsg-summary="true">
        <ul>
            <li>error for foo</li>
            <li>error for bar</li>
            <li>error for hoge</li>
        </ul>
    </div>
</fieldset>

<fieldset>
    <legend>Html.ValidationMessage</legend>
    <span class="field-validation-error" data-valmsg-for="foo" data-valmsg-replace="true">error for foo</span><br />
    <span class="field-validation-error" data-valmsg-for="bar" data-valmsg-replace="true">error for bar</span><br />
</fieldset>

 

 

組み込みHTMLヘルパを使わず自力で出力

多くの場合は組み込みのHTMLヘルパを利用すれば事足るかもしれません。が、例えば、出力するHTMLをフルにカスタマイズする必要に迫られることもあるかもしれません。

そのような場合、ViewDataModelStateプロパティ(ModelStateDictionary型)の中身を直接読み取ることで、バリデーションエラーの内容を取得することができます。

 

たとえば以下のようにすると、積み込まれているバリデーション エラーを列挙することができます。

<fieldset>
    <legend>ViewData.ModelStateから抽出</legend>
    @{
        foreach (var key in ViewData.ModelState.Keys)
        {
            foreach (var e in ViewData.ModelState[key].Errors)
            {
                <span>@key : @e.ErrorMessage</span><br />
            }
        }
    }
</fieldset>

 

出力されるHTMLは以下のようになります

<fieldset>
    <legend>ViewData.ModelStateから抽出</legend>
    <span>foo : error for foo</span><br />
    <span>bar : error for bar</span><br />
    <span>hoge : error for hoge</span><br />
</fieldset>

アプリ内で再利用するようであれば、カスタムのHTMLヘルパを作成してもよいですね。

 

サンプルソース

サンプルソースgithubに置いておきました。 kiyokura/MvcValidationSample01

*1:若干、改行とインデントを修正しています

これからASP.NETを始める人が読むべき本

これからASP.NETをはじめる人に、『まずこの本を読みましょう』と即答できる書籍がようやく発売されました。

.NET開発テクノロジ入門 2014年版 VisualStudio2013対応版 (MSDNプログラミングシリーズ)

.NET開発テクノロジ入門 2014年版 VisualStudio2013対応版 (MSDNプログラミングシリーズ)

今年の6月4日に発売されたこの本、5末に開催されたde:codeの会場で先行販売されていたのをゲットしていたのですが、実は今日になって初めてまともに目を通しました(すみません)。

 

タイトルからして、.NET開発全般を広く浅く紹介してると思えるかもしれません*1。が、版社である日経BPのサイトの目次を見ていただけるとわかるのですが、第6章以外はほぼほぼ、ASP.NETもしくはASP.NETでも押さえておくべきテクノロジとなっています*2

 

なので、この書籍は『One ASP.NET入門』と思って読んでも差しさわりにない無いないようになっていると思います。というかむしろ、まずはこの書籍を読みましょう。

 

 

ASP.NET MVCを深く学ぶには…

特に『ASP.NET MVCの開発をこれから始めよう』と思った場合、まずはこの書籍の1章~3章を読んだうえで、公式サイトのチュートリアル(※ただし英語)をやってみるというステップが良いのではないかと思います*3

その上で、ASP.NET MVCについてもっと深く知りたい場合は、こちらの書籍がお勧めできます。

プログラミングMICROSOFT ASP.NET MVC (Microsoft Press)

プログラミングMICROSOFT ASP.NET MVC (Microsoft Press)

 

この書籍の内容はMVC 3準拠で記述されています*4。が、MVC 5であっても概念や仕組みについてはMVC 3の知識がほぼそのまま生かせます。またこの書籍は『初心者向けのチュートリアル』や『TIPS集』的なものではありませんASP.NET MVCの概念や仕組み、これを使ってアプリケーションを実装・設計する際に必要な知識について述べている書籍です。そういったこともあって、少なくともMVC 5世代では大半の記述内容がそのまま通用すると思います。

(現在、翻訳元のほうはMVC 5対応のバージョンの isbn:1430265299 が発売されていますが、基本的にはMVC 5での差分が加筆されている程度のように見えますし。)

 

ということで、重ねてになりますが、ASP.NET MVCの日本語書籍が欲しい方は、まずは一度チェックしてみてください。

*1:何を隠そう私も最初はそう思っていました

*2:Entity FrameworkもMSの雰囲気的には、どちらかというとASP.NET寄りと思っても差しさわりが無い感じだったりもしますし。

*3:Entity FrameworkはASP.NET MVCの必須テクノロジーではありませんが、公式チュートリアルの大半はEntity Frameworkを利用しています。そのため、『チュートリアルをちょっと改造して何かしよう』とか思った際にも、Entity Frameworkの基礎をある程度は知っておかないと変なはまり方をする恐れがあるんじゃないかと思ってます

*4:MVC 4についても巻末で補足されています