redwarrior’s diary

C#, ASP.NET, WEB, G*などの雑多な情報

AutoMapper は使わない!

ASP.NET MVC で開発をしている時に気になってくるのが、Entity Framework で作成したエンティティと画面で使用するモデル(ViewModel)の詰め替えをどうするかということ。

少しググってみると、AutoMapper を使用すると良いと出てくる。プロパティを一つずつコピーするのはいかにも冗長な作業でなんとかしたいと思うのはわかるし、一行でプロパティのコピーが出来るのは便利だと思う。ただし、これはやりたくない。

.NET ではなく Java の話だけど、DXO(Data Exchange Object)というものが話題になったことがあった。その時に DBFlute というフレームワークを作っている id:jflute さんの書いていたことが印象に残っている。
DXO を提供するツールを使うことで開発は少し削減されるかもしれないが、エラー発生時にデバッグで悩んでしまえばトータルではマイナスになるという趣旨の内容だったと思う。
その後調べた範囲では DXO を使ったツールはそれほど流行らなかったようで、この主張は的を得ていると思った。

AutoMapper はその時の DXO .NET で実現するものだと思えるので、使わない方が良いと思っている。
しかし、冗長なコードを書くのもまた面倒なのでどうしたものかなと思い、でも、C# には LINQ があるからそんなに冗長にはならないのではないかとも思った。
今回、実際に試してみた結果、想像以上にスッキリと書けて、マジで AutoMapper は使わなくても良いと思えたので、ここに方法を記す。

前置きがものすごい長くなったが、ソースコードで表すとこんな感じ。

モデルクラス(Book.cs)

public class Book
{
    public string Title { get; set; }
    public Author author { get; set; }
}

モデルクラス(Author.cs)

public class Author
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

ViewModel(BookViewModel.cs)

public class BookViewModel
{
    public string Title { get; set; }
    public string AuthorName { get; set; }
}

詰め替え処理

//単独オブジェクト
BookViewModel vmodel = BuildViewModel(book);

//コレクション
IEnumerable<BookViewModel> vmodels = books.Select(BuildViewModel); //booksはIEnumerable<Book>

//詰め替えを行うメソッド
private static BookViewModel BuildViewModel(Book book)
{
    return new BookViewModel
    {
        Title = book.Title,
        AuthorName = book.FirstName + " " + book.LastName,
    };
}

staticの詰め替え用のメソッドを用意する必要はあるが、呼び出す側は単独オブジェクトだろうが、コレクションだろうが1行で済む。

プロパティ名の異なるクラス間の変更や、コレクションの変更では、AutoMapperでも対応付けるための記述が必要になるので、AutoMapperを使って手間を省けるのはプロパティが同じクラス間だけなのではと思う。

プロジェクト都合にも寄るのかもしれないけど、デバッグ時のわかりやすさと天秤にかけると、自分はAutoMapperはいらないかなと思った。

ASP.NET MVC でテキストボックスとラベルに別の書式を使用する

ASP.NET MVCでは、ビューヘルパーを使用することで、テキストボックス等のフォーム部品をシンプルに記述することが出来ますよね。

さらに、***Forメソッドを使用すると、モデルクラスのプロパティを扱うことが出来て、EditorForメソッドやDisplayForメソッドではプロパティに指定した属性によってデフォルトの表示を変更することが出来ます。

例えば、以下のように日付や小数点の書式を指定したりします。(ASP.NET MVC5で確認しました。)

モデルクラス

[DisplayName("公開日")]
[DisplayFormat(DataFormatString = "{0:yyyy/MM/dd}", ApplyFormatInEditMode = true)]
public DateTime Published { get; set; }

[DisplayName("小数点を含む数値")]
[DisplayFormat(DataFormatString = "{0:0.000}", ApplyFormatInEditMode = true)]
public decimal Value { get; set; }

ビュー(編集画面)

@Html.EditorFor(model => model.Published)
@Html.EditorFor(model => model.Value)

ビュー(一覧画面)

@Html.DisplayFor(model => model.Published)
@Html.DisplayFor(model => model.Value)

この場合、ApplyFormatInEditModeが true に設定されているので、テキストボックスにも、ラベル(プロパティの値を文字列として出力することをここでは指します)にも同じ書式で表示されます。(公開日:2016/08/22、小数点を含む数値:1089.123)

課題

仕様変更で、テキストボックスとラベルで別の書式を使用することになりました。どうすれば良いでしょう?

ちなみに、以下のようにモデルに複数する方法はDisplayFormat属性が重複するのでダメでした。

[DisplayName("公開日")]
[DisplayFormat(DataFormatString = "{0:yyyy年MM月dd日}", ApplyFormatInEditMode = false)] //表示はyyyy年MM月dd日にしたい
[DisplayFormat(DataFormatString = "{0:yyyy/MM/dd}", ApplyFormatInEditMode = true)]
public DateTime Published { get; set; }

[DisplayName("小数点を含む数値")]
[DisplayFormat(DataFormatString = "{0:#,0.000}", ApplyFormatInEditMode = false)] //表示はカンマ区切りにしたい
[DisplayFormat(DataFormatString = "{0:0.000}", ApplyFormatInEditMode = true)]
public decimal Value { get; set; }

対処法

調べた結果、以下の方法で書式を分けることが出来ました。2か所変更します。

  1. モデルのDisplayFormat属性のApplyFormatInEditModeをfalseにする
  2. EditorForメソッドを使用せず、TextBoxForで書式を指定する。

変更後はこうなります。ビュー(一覧画面)に変更はありません。

モデルクラス

[DisplayName("公開日")]
[DisplayFormat(DataFormatString = "{0:yyyy年MM月dd日}", ApplyFormatInEditMode = false)]  //表示はyyyy年MM月dd日にしたい
public DateTime Published { get; set; }

[DisplayName("小数点を含む数値")]
[DisplayFormat(DataFormatString = "{0:#,0.000}", ApplyFormatInEditMode = false)] //表示はカンマ区切りにしたい
public decimal Value { get; set; }

ビュー(編集画面)

@Html.TextBoxFor(model => model.Published, "{0:yyyy/MM/dd}") //入力はyyyy/MM/dd
@Html.TextBoxFor(model => model.Value, "{0:0.000}") //入力はカンマ区切りなし

これでテキストボックスとラベルに別の書式を設定することが出来ました。

応用編

ビューを修正したくない場合は、独自テンプレートを用意することでビューの修正を無しにできます。

DateTime.cshtml

@model DateTime?

@Html.TextBoxFor(model => model, "{0:yyyy/MM/dd}") //入力はyyyy/MM/dd

decimal.cshtml

@model decimal?

@Html.TextBoxFor(model => model, "{0:0.000}") //入力はカンマ区切りなし

大した修正ではないですが、知っておくと地味に役立ちます。

ASP.NET MVC で備考入力欄を作成する方法のまとめ

久しぶりの更新になってしまった。ブログを書く時間を意識してとるように気をつけよう。

入力フォームを作る時に、大体作ることになる備考入力欄について、ASP.NET MVC での実装方法のポイントをまとめてみました。

はじめに

ASP.NET MVC 5で動作を確認しています。Scaffolding機能を使用して、Model,Controller,Viewを作成しておいてください。

備考を格納するプロパティはstring型を想定しています。

textareaタグで表示する

デフォルトではテキストボックス(<input type="text">)で表示されるのを、テキストエリア(<textarea>)で表示するように変更します。

[方法] モデルクラスのプロパティにDataType.MultilineTextを指定したDataType属性を設定します。

public class Member
{
    //他のプロパティは省略
   
    [DisplayName("備考")]
    [DataType(DataType.MultilineText)]
    public string Note { get; set; }

複数行で表示する

textareaタグで表示しただけでは、見た目がテキストボックスと変わらないため複数行での入力をできるようにします。

[方法] ビュー(cshtml)のHtml.EditorForメソッドの引数にrowsを追加します。今回は4行で表示します。

@Html.EditorFor(model => model.Note, new { htmlAttributes = new { @class = "form-control", rows = "4" } })

備考欄にフォーカスした時に文字入力を日本語入力に変更する

見た目は良くなりました。数値を入力するテキストボックス等から、備考欄にフォーカスが移った場合は日本語入力が可能になっていると便利なので設定を行います。

[方法] ビュー(cshtml)のHtml.EditorForメソッドの引数にスタイルシートとしてime-mode: active;を設定します。

@Html.EditorFor(model => model.Note, new { htmlAttributes = new { @class = "form-control", rows = "4", style = "ime-mode: active;" } })

あるいは直接指定せずに、Site.css(元から用意されているcssファイル)に書き込んだ方が便利かもしれません。

textarea.form-control {
    ime-mode: active;
}

ime-modeは他にも値があるので、テキストボックスとテキストエリアに別の値を設定することで、フォーム入力少し便利になりそうです。

参考サイト:

www.htmq.com

(小ネタ)C#でデフォルト引数に今日の日付や現在時刻を指定する

ちょっと前ですが、以下の記事を読みました。あわせて読みたいの記事も読みましたが、どれも丁寧にまとめられていて良い内容でした。

techlife.cookpad.com

日付を渡すには、Rubyではデフォルト引数で行うのが簡単と書かれていたので、これをC#でやってみようと思いました。

以下のようにデフォルト引数に今日の日付を設定したところ、コンパイルエラーになってしまいました。

string GetData(DateTime? target = DateTime.Today) {
    // targetを使った処理
}

エラーメッセージ

'target' の既定パラメーター値は、コンパイル時の定数である必要があります。

これはDateTime.Todayだけでなく、DateTime.Nowでも同じ。これはデフォルト引数で「今日の日付」とか「現在時刻」を使用することは出来ないってこと?

そんな不便な!と思ったのですが、簡単に回避する方法がありました。それが以下です。

string GetData(DateTime? target = null) {
    if (!target.HasValue)
    {
        target = DateTime.Today;
    }

    // targetを使った処理
}

デフォルト引数にはコンパイル時定数しか設定できないので、デフォルト引数にはnullを設定しておいて、メソッド内でnull判定をして、nullだったら今日の日付を設定する。

パラメータにnullを渡した場合も同じ動作となってしまいますが、今のところこれで困ったことは特にないです。 これでC#でもつらさを軽減できますね。

余談ですが、テストの実行には Chaining Assertion を使ってます。便利。

chainingassertion.codeplex.com

TFS 2015でプロジェクトを.NET Framework 4.6にダウングレードしてビルドする

前振り

前回の記事で.NET Framework 4.6.1を使用してビルドが出来るようになったので、次にテスト環境を用意しようと思いました。

redwarrior.hateblo.jp

いきなりTFS上からテスト環境へデプロイをするのは大変そうなので、まずはローカルマシンからデプロイをすることに。

テスト環境に.NET Framework 4.6.1は入っていないので、まずそこからインストールしようとしたところ、サポートOSにテスト環境のOSが含まれていない。テスト環境は無印のWindows Server 2008でした。

Windows Server 2008 R2ならOKなのですが、インフラ部隊によるとR2は全く別のOSなので、OS変更は出来ないと。

さらに調べるとWindows Server 2008は、.NET Framework 4.6ならば対応している。

.1の違いでサポートOSが変わるとは思いませんでしたが、そういった理由で、プロジェクトの対象フレームワーク.NET Framework 4.6に変更することになりました。

変更後のローカルマシンからのデプロイは成功し、起動も確認できましたが、ここでタイトルの話です。このプロジェクトをTFS上でビルドしようとすると以下のエラーメッセージが出てビルドに失敗します。(メッセージは改行を入れています)

 C:\Program Files (x86)\MSBuild\14.0\bin\amd64\Microsoft.Common.CurrentVersion.targets (1098): フレームワーク ".NETFramework,Version=v4.6" の参照アセンブリが見つかりませんでした。
これを解決するには、このフレームワーク バージョンの SDK または Targeting Pack をインストールするか、
SDK または Targeting Pack をインストールしているフレームワークのバージョンにアプリケーションを再ターゲットしてください。
アセンブリはグローバル アセンブリ キャッシュ (GAC) から解決され、参照アセンブリの代わりに使用されるため、
アセンブリが目的のフレームワークに正しくターゲットされない場合もあります。

.NET Framework 4.6.1は、インプレース更新されるので、そのままビルド出来るかと思いましたが、そうもいかないみたいです。

このままでは、開発環境(.NET 4.6)、TFS(.NET 4.6.1)、テスト環境(.NET 4.6)とややこしいので、TFSでも.NET 4.6でビルド出来る方法を調べました。(長い前振り終わり)

本題

今回やったことは以下の通り。1つ前の記事に比べるとずっと作業は少なかったです。

.NET Framework 4.6 のTFSへのインストール

試しにやってみましたが、既にインストールされているとか、新しいものがインストール済み等のメッセージが出て続行することが出来なかったです。まあ、これは想定通り。

.NET Framework 4.6 Targeting Pack のTFSへのインストール

.NET Framework 4.6にDeveloper Packは無かったので、.NET Framework 4.6 Targeting Packをインストールしました。

Download Windows Vista SP2、Windows 7 SP1、Windows 8、Windows 8.1、Windows Server 2008 SP2、Windows Server 2008 R2 SP1、Windows Server 2012、Windows Server 2012 R2、および Windows 10 用 Microsoft .NET Framework 4.6 Targeting Pack および言語パック from Official Microsoft Download Center

以上。

つまり、Targeting Packのインストールだけでした。思ったより、あっさりとビルド出来るようになったので良かったです。

その他

多分、.NET Framework 4.5.2のプロジェクトをビルドするときも、.NET Framework 4.5.2 Developer Packをインストールすれば上手く行きそうな気がします。必要ないのでしませんが。

再びビルド出来るようになったので、次は自動デプロイを試してみたいです。

TFS 2015で.NET Framework 4.6.1のプロジェクトをビルドする

新プロジェクトが開発に入るにあたって、どうせならばTFSでビルド出来るようにして、ゆくゆくはプルリクエストがマージされたらテスト環境にデプロイされるようにしたい。

またVisual Studio 2015で開発するのだから、.NET Frameworkも4.6.1を使い、作成するのはWebアプリなのでASP.NET MVC 5で作ることになった。

早速、テンプレートを選択して新規プロジェクトを作成した。

実際に開発を始める前にビルド環境を整えた方が問題の切り分けも楽なので、ローカルでビルドと実行に問題ないことを確認後、バージョン管理に登録しTFS上でのビルドを試した。

特に設定を変更せずにXAMLビルドを新規作成して実行すると動かない。それで色々と調べたり設定を追加して、ようやく動くようになったのでやったことを記録しておく。

前提

クライアント環境
サーバー環境

(?は、TFSのインストールには関わっていないため)

注意事項

(試してないが)Visual Studio 2015をTFSが起動しているサーバーにインストールするのであれば、以下の手順は不要になると思われる。

またTFSの初期設定などは完了していて、.NET Framework 4.5のプロジェクトではビルドが通っている状態とする。

作業内容

やった作業を整理すると以下のようになった。

.NET Framework 4.6.1 Developer Pack のTFSへのインストール

ネット上では.NET 4.6.1 Targeting Pack が必要という情報が見つかるが、上記に含まれている。

Download Windows 7 SP1、Windows 8、Windows 8.1、Windows 10、Windows Server 2008 R2 SP1、Windows Server 2012、Windows Server 2012 R2 用 Microsoft .NET Framework 4.6.1 Developer Pack および Language Pack from Official Microsoft Download Center

Microsoft Build Tools 2015 のTFSへのインストール

Visual Studio 2015で使用されているバージョンのMSBuildが単体パッケージとして独立したもの。

Download Microsoft Build Tools 2015 from Official Microsoft Download Center

ビルド定義の編集で、「プロセス→ビルドプロセスパラメータ→詳細設定」で、MSBuild 引数に /tv:14.0 を設定

上記でインストールしたMSBuildをTFSに使用させるためのオプションのようだ。

必要なファイルのコピー

ASP.NET MVC のビルドをしたい場合は、以下が必要。

  • ローカルPCの C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\WebApplications フォルダを TFS の同じフォルダにコピーする。

Web Deployをしたい場合は、以下が必要。

  • ローカルPCの C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\Web フォルダを TFS の同じフォルダにコピーする。

その他

ちなみに上記を行っていない場合は.NET 4.5.2のプロジェクトのビルドは失敗し、.NET 4.5.1のプロジェクトのビルドは成功する。これはWindows Server 2012 R2にデフォルトで含まれているのが、.NET 4.5.1だからのようだ。詳しくは以下。

.NET Framework のバージョンおよび依存関係 | Microsoft Docs

TFSをインストールしただけで、ビルドやテスト実行が出来ると思い込んでいたので気をつける。

VSUG DAY FINALに参加してきた。

イベント参加レポートが毎回遅くなってしまった。またも2カ月くらい前になってしまいましたが、参加した感想を書いておきます。

FINALということで、過去をふりかえる内容がありましたが、自分は初めてなので、そんなこともあったんだーというくらいの気持ちで聞いていました。

最新情報の提供というのはありましたが、書いているのが2カ月後なのでそれを書いても新鮮味はないと思います。

ということで、今後の役に立つかもしれないアーキテクトによるパネルディスカッションの感想を書きます。

パネルディスカッションは、マイクロソフトの榊原さん、萩原さん、JJUG会長の鈴木さん、VSUGの小井土さんの4人で行われました。モデレーターはVSUGの福井さんです。

各氏の肩書などは公式サイト(VSUG DAY ~THE FINAL~)を参照してください。

スライド:

www.slideshare.net

ビジネス要求に対してどのように技術をマッピングするか?

ここでは「ステークホルダーは誰がいるか?非機能要件のとりまとめをどうするか?」という話が出て、それに対して「ユーザー数、扱うデータ量、トランザクションの有無のどれを柔軟にするのかを考える」という答えがあったり、 「売り文句になるものを用意する。営業マンとユーザーで必要なものは変わる」という話が興味深かったです。

ここから要求の聞き取りをどうやるかという話に発展して、「エンジニアは広く見渡すことが出来るので、ビジネスのモデリングを行う」ことや「チームとして全体的な方針を作る」こと。その中で「30年前作られたの複雑なロジックで現代ではいらないものを捨ててもらうために、当時はなぜ必要になったのかを聞き、改めて現在必要なのかを問い合わせる」などの具体策が語られたり、「技術者ではない経営者は技術についてわからないから安全を重視する。シンプルに説明することが必要」というアドバイスがありました。

どのように新技術をキャッチアップしているか?

ここでは自分の得意分野をもつこと、アンテナを広げること、面白そうなものは試してみること、ネットワークをたどって興味を広げることなどが話されました。

SNSの活用がカギだと感じました。その上で「新技術がこなれたかの境界を見ている。最新技術で開発しているエンジニアや企業がいるからといって使えるかどうかはわからない」とか「競合を見る。MySQLSQL Serverなど関連性・多様性を学べる」とか参考になる見方があると感じました。

一方でコミュニティなどで直接聞いたりディスカッションするという話もあり、人のつながりも侮れないと思いました。

最もコスパが良いのは本を読むことであり、勉強会をみんなでやったりしながら、学んだ技術を見直して自分頭で考えるなど積み重ねる以外にショートカットはないというのはそうだなと。

新しい技術をアーキテクチャに適用するタイミングは?

ここでは「ステークホルダーに説明できるか」、「責任を取る覚悟があるか。出来ないことは出来ないという。詳しい人がいるか」や、「コンポーネント単位であればこっそり適用してしまう。ただしリードアーキテクトを除く」などの心強い話もあったり、一方「セキュリティは最初に考える」とか「運用コストの視点を持つ」とか「バランスを取るのは難しい。周りの開発者もステークホルダーと考える」などの考えさせられる話も。

若い開発者に伝えたいことは?

ここはあまりメモを取れなかったのですが、「説明できることの大切さ」が言われていました。

うまくまとめられていませんが、あまり他では聞くことが出来ないメンバーで話を聞けたのが良かったです。JJUG会長の鈴木さんが榊原さんを尊敬していたのが印象的でした。