redwarrior’s diary

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

ELMAH と Glimpse を組み合わせて使用する

ASP.NETデバッグ作業目的に使用するツールとして、ELMAH と Glimpse がある。

ELMAH に関しては、「プログラミングMicrosoft ASP.NET MVC 第3版ASP.NET MVC 5 対応版」で知りました。

Glimpse については、以下のサイトで知りました。

両方とも便利そうなので、合わせて導入しようとしたら、ちょっとコツが必要だったので記録に残す。

インストール方法

ELMAH

NuGetでELMAH.MVCをインストールする。

Glimpse

NuGetでGlimpse.Mvc5、Glimpse.EF6をインストールする。

ELMAHとGlimpseを組み合わせる

GlimpseからELMAHを参照できるようにするツールをインストールする。具体的には、NuGetでGlimpse.Elmahをインストールする。

この状態で、ELMAHを参照しようとすると以下のJavaScriptエラーがVisual Studio上で発生します。

f:id:redwarrior:20161009200331p:plain

[続行する]をクリックすれば、いちおう画面を見ることは出来ます。また、GlimpseのElmahタブからも概要は見れます。

しかし、やはりエラーメッセージは無くしておきたい。

対処方法

エラー内容からELMAHの画面で発生しているようなので、Glimpseの対象からELMAHを外します。以下のようにurisタグで指定したパスを除外できます。

<glimpse defaultRuntimePolicy="On" endpointBaseUri="~/Glimpse.axd">
  <!-- 
        For more information on how to configure Glimpse, please visit http://getglimpse.com/Help/Configuration
        or access {your site}/Glimpse.axd for even more details and a Configuration Tool to support you. 
    -->
  <runtimePolicies>
    <uris>
      <add regex=".*/elmah"/>
    </uris>
  </runtimePolicies>
</glimpse>

これでELMAHのページを直接開いた場合も、GlimpseからELMAHのページを開いた場合もエラーがなくなります。

盲点

ここまで設定して導入したELMAHですが、Glimpseに比べて実はあまり使っていません。

なぜかというと、運用フェイズに入っていないと言うのもありますが、Unity(DIコンテナの方)を使うと、スタックトレースがDI対象のインターフェースまでしか出ないため、YSOD(Yellow Screen of Death)を見てもエラーが特定できないためです。

それでも、いつエラーが起きたかを後から見返せるので、設定しておいて損はないと思います。

動作環境

  • Visual Studio 2015 Professional
  • ASP.NET MVC 5.2.3
  • Entity Framework 6.1.3
  • Elmah.Mvc 2.1.2
  • Glimpse.Mvc5 1.5.3
  • Glimpse.EF6 1.6.5
  • Glimplse.Elmah 1.1.1

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をインストールしただけで、ビルドやテスト実行が出来ると思い込んでいたので気をつける。