しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET MVC の XSS 脆弱性 CVE-2014-4075 について調べた

昨日は SSL 3.0 の脆弱性以外に ASP.NET MVC の脆弱性に関しても情報が公開された日でした。

2014 年 10 月のセキュリティ情報 (月例) - MS14-056 ~ MS14-063 - 日本のセキュリティチーム - Site Home - TechNet Blogs
MS14-059: Description of the security update for ASP.NET MVC 5.1: October 14, 2014

影響を受ける ASP.NET MVC のバージョンは 2.0-5.1 とかなり広くなっています。既に NuGet でも 3.0-5.1 までは修正版が公開されているので、該当するバージョンを使っている場合には早めにアップデートしておきましょう。ちなみに最新の MVC 5.2 は影響を受けません。

NuGet Gallery | ASP.NET MVC 3 3.0.50813.1
NuGet Gallery | Microsoft ASP.NET MVC 4 4.0.40804
NuGet Gallery | Microsoft ASP.NET MVC 5.0.2
NuGet Gallery | Microsoft ASP.NET MVC 5.1.3

MVC 2.0 に関しては NuGet が存在していない時のバージョンなので、ダウンロードセンターから手動でのアップデートが必要になります。

Download Microsoft ASP.NET MVC セキュリティ更新プログラム MS14-059 (KB2990942) from Official Microsoft Download Center

と言っても、手動でインストールしなくても、殆どの場合は Windows Update で GAC の System.Web.Mvc.dll が自動的に更新されるので問題無いでしょう。

ちなみに NuGet でインストールしていたとしても、サーバーの GAC にアセンブリが入っていればそちらが優先されるので、アプリケーション側のアップデートをすることなく Windows Update だけで完結出来る環境もありそうです。*1

Azure Web サイトの場合

Azure Web サイトには MVC 3.0 のアセンブリだけが GAC にインストールされているので、Windows Update で自動的に反映されるのは 3.0 だけになるかと思います。

基本的に NuGet でインストールした MVC 4.0-5.1 を使っているアプリケーションは、NuGet から最新版をインストールしてからの再デプロイが必須になると考えています。

脆弱性の内容について

調べてみたところ、どうやらこのコミットで脆弱性が修正されたようです。

http://aspnetwebstack.codeplex.com/SourceControl/changeset/2b12791aee4ffc56c7928b623bb45ee425813021

内容としては特定の条件の下で HTML ヘルパーを使っていた時に、HTML エンコードが行われない問題を修正したようです。変更点をまとめると以下のような感じ。

  • ModelMetadata に HtmlEncode プロパティを追加
    • デフォルトは HtmlEncode = true
  • DisplayFormat 属性の HtmlEncode プロパティを見る
  • DisplayFor / EditorFor の ObjectTemplate でテンプレートの深さが 1 以上の時に HtmlEncode プロパティを参照してエンコードの有無を切り替える
  • DisplayText / DisplayTextFor では ModelMetadata の HtmlEncode プロパティを参照してエンコードの有無を切り替える

ぶっちゃけ良く分からない。

とりあえず ObjectTemplate に関しては ModelMetadata の SimpleDisplayText プロパティの扱いに注意。DisplayText / DisplayTextFor に関しては、元々 HTML エンコードしないのが仕様だと思ってたんですが、これからは基本的に明示的に HTML エンコードを無効にしない限りは全てエンコードされるようになりました。

HTML を直接出力したい場合には Html.Raw を明示的に呼び出すか、DisplayFormat 属性の HtmlEncode を明示的に false にしましょうと言う話でした。*2

まとめ

ASP.NET MVC に関して CVE で脆弱性が上がるのは初めてだと思います。

基本的に ASP.NET は XSS に対して、リクエスト時点での検証、デフォルトで全て HTML エンコードされる Razor などで防御を固めていますが、今回は仕様がいまいちなヘルパーが原因だった感じです。

後は @chack411 がきっとブログなどで補足してくれるはずです。

*1:少なくとも自分の環境では 5.1.2 をインストールしていても GAC の 5.1.3 が読み込まれた

*2:DisplayText / DisplayTextFor を使っている人がいるのかは疑問

Windows Server Technical Preview の IIS というか http.sys が HTTP/2 に対応していたらしい

Windows Server Technical Preview には正直あまり興味が無かったんですが、今日に IIS でも HTTP/2 が使えたという話が Twitter で流れてきたので早速 Azure の仮想マシンに入れて試しました。

SPDY も HTTP/2 も SSL 上に作られているので、基本的に https が必須です。つまり IIS で https のバインディングを追加しないと有効にならないので気が付きにくそうです。

f:id:shiba-yan:20141006213345p:plain

こんな感じで https のバインディングを追加してあげれば、勝手に HTTP/2 が使われるようになりました。

ちなみに HTTP/2 の Draft 13 に対応している Chrome だと有効にならなかったので、IIS はラストコールになった Draft 14 を実装しているようです。

HTTP/2がHTTPbisワーキンググループのラストコールに - Publickey

何となくメジャーなブラウザと HTTP/2 で通信できるのか確認してみました。

Internet Explorer

Windows 10 の IE から HTTP/2 に対応したことが IEBlog にて公表されてます。

HTTP/2

Internet Explorer on the Windows 10 Technical Preview includes support for new the HTTP/2 networking protocol which is a standardized effort with broad industry support. HTTP/2 builds on our experience delivering SPDY/3 support in IE11 and enables improved performance on the Web using techniques including multiplexing, header compression and Server Push.

Internet Explorer and the Windows 10 Technical Preview - IEBlog - Site Home - MSDN Blogs

F12 開発ツールでネットワークを確認すると HTTP/2 で通信していることが分かります。

f:id:shiba-yan:20141006213344p:plain

IE11 では SPDY/3 に対応しつつもサーバー側が非対応だったので、次期 Windows Server のリリースに期待が高まります。

Chrome

最初にベータリリースの 38 を使って SPDY/4 を有効にした状態で試しましたが、残念ながら HTTP/2 は有効になってくれませんでした。既に書いたように Draft 14 を IIS が実装していたので、そこで違いが発生していたようです。

なので Chrome の Canary 版を入れてみたところ、問題なく HTTP/2 で通信が行えました。

f:id:shiba-yan:20141006213342p:plain

Firefox

Nightly ビルドをインストールして確認したところ、特に設定も必要なく HTTP/2 で通信が行えました。

f:id:shiba-yan:20141006213343p:plain

http.sys 絡み

IIS の HTTP 周りは http.sys というドライバが処理を受け持っているはずなので、同じ http.sys を使う HttpListener でも HTTP/2 が有効になるのではと思ったので確認してみました。

f:id:shiba-yan:20141006222338p:plain

予想通り https の場合には HTTP/2 が有効になったので、IIS というよりも http.sys が HTTP/2 に対応したというのが正しいです。

f:id:shiba-yan:20141006213341p:plain

当然ながら ASP.NET アプリケーションでも、特別な対応無しに HTTP/2 の恩恵に与れます。

ASP.NET 4.5.2 で追加されたバックグラウンド処理 API を使って待つ必要のない非同期処理を丸投げする

.NET Framework 4.5.2 向けのアプリケーションで Google API クライアント周りがコンパイルエラーになって調べていたときに、そういえば ASP.NET に API が追加されたことを思い出したので調べてみました。

ちなみに .NET Framework 4.5.2 で追加された ASP.NET 向けの機能は以下のようになっています。

New APIs for ASP.NET apps. The new HttpResponse.AddOnSendingHeaders and HttpResponseBase.AddOnSendingHeaders methods let you inspect and modify response headers and status code as the response is being flushed to the client app. Consider using these methods instead of the PreSendRequestHeaders and PreSendRequestContent events; they are more efficient and reliable.


The HostingEnvironment.QueueBackgroundWorkItem method lets you schedule small background work items. ASP.NET tracks these items and prevents IIS from abruptly terminating the worker process until all background work items have completed. This method can't be called outside an ASP.NET managed app domain.


The new HttpResponse.HeadersWritten and HttpResponseBase.HeadersWritten properties return Boolean values that indicate whether the response headers have been written. You can use these properties to make sure that calls to APIs such as HttpResponse.StatusCode (which throw exceptions if the headers have been written) will succeed.

What's New in the .NET Framework 4.5, 4.5.1, and 4.5.2

AddOnSendingHeaders とか HeadersWritten をアプリケーションから直接使う場面はそこまで多くない気がしますが、HostingEnvironment に追加された QueueBackgroundWorkItem は利用価値が高いと思います。

既に .NET Web Development and Tools Blog にて紹介されています。すっかり存在を忘れていました。

QueueBackgroundWorkItem to reliably schedule and run background processes in ASP.NET - .NET Web Development and Tools Blog - Site Home - MSDN Blogs

紹介されている通り、メール送信というような結果を await で待つ必要が無い処理の場合には、QueueBackgroundWorkItem を使うことでレスポンスを素早く返しつつ、時間のかかる処理を完了させることが出来るようになります。

とりあえず簡単なサンプルコードを載せておきます。

public ActionResult Index()
{
    // これは良くない -> http://neue.cc/2013/07/02_412.html
    //ProcessAsync(new CancellationToken());

    HostingEnvironment.QueueBackgroundWorkItem(token => ProcessAsync(token));

    return View();
}

private async Task ProcessAsync(CancellationToken cancellationToken)
{
    Debug.WriteLine("executing");

    // 時間のかかる処理のつもり
    await Task.Delay(10000, cancellationToken);

    Debug.WriteLine("executed");
}

QueueBackgroundWorkItem を使って実行されるタスクには実行コンテキストが渡されないので、ConfigureAwait(false) を呼び出した状態と同じになるようです。これだけだと使うメリットが無さそうですが、QueueBackgroundWorkItem で登録したタスクは CancellationToken が渡されるので、タスク内でキャンセル時の処理を組み込んでおくことが出来ます。

そして ASP.NET の AppDomain がシャットダウンされる時に QueueBackgroundWorkItem で追加されたタスクが残っている場合、デフォルトで 90 秒間はシャットダウンを待つようになっています。

挙動を実際に確認しておきます。まずは QueueBackgroundWorkItem を使わずに Task を投げっぱなしで実行し、IIS Express をシャットダウンした場合のログです。

f:id:shiba-yan:20140831125216p:plain

await 後のログが出力されていないので、IIS Express のシャットダウンによって実行中のスレッドも強制的にシャットダウンされたようですね。

今度は QueueBackgroundWorkItem を使って実行した場合のログです。

f:id:shiba-yan:20140831125346p:plain

CancellationToken を Task.Delay に渡しているので、Task.Delay の処理が途中でキャンセルされて TaskCancelledException が投げられています。

最後は QueueBackgroundWorkItem を使い、Task.Delay に CancellationToken を渡さない場合のログです。

f:id:shiba-yan:20140831125910p:plain

CancellationToken を渡さなかったので Task.Delay はキャンセルされることなく実行されましたが、強制的に AppDomain がシャットダウンされることなく、完了後に IIS Express が終了していることがわかります。

ASP.NET 4.5.2 の環境では普通に ThreadPool を使って実行するよりも、QueueBackgroundWorkItem を使った方が安全かつ確実に処理を行えそうです。

Azure Web サイトの .NET Framework が 4.5.2 にアップデートされていた話 - しばやん雑記

.NET Web Development and Tools Blog の記事では Azure Web サイトはまだ対応していないと書いてありますが、既に 7 月の時点で .NET 4.5.2 へのアップデートが行われたので、管理ポータルの .NET Framework のバージョンを V4.5 に変更すれば使えるようになっています。

ASP.NET MVC 5.2.2 / Web API 2.2.2 / Web Pages 3.2.2 がリリースされました

先月の終わりに ASP.NET MVC / Web API / Web Pages がアップデートされていました。

NuGet Gallery | Microsoft ASP.NET MVC 5.2.2
NuGet Gallery | Microsoft ASP.NET Web API 2.2 5.2.2
NuGet Gallery | Microsoft ASP.NET Web Pages 3.2.2

リリースノートは出ていませんが、MVC 5.2.2 に関しては Web Pages との依存関係のためにバージョンを上げただけのようです。なので新機能の追加やバグ修正は行われていません。

Microsoft.AspNet.MVC 5.2.2

This release doesn’t have any new features or bug fixes in MVC. We made a change in Web Pages for a significant performance improvement and have subsequently updated all other dependent packages we own to depend on this new version of Web Pages.

What’s New in ASP.NET MVC 5.2 | The ASP.NET Site

Web Pages 3.2.2 に関しては ASP.NET Web Pages 3.2.1 のベータ版がリリースされました - しばやん雑記 でも紹介した Razor のレンダリング時に LOH を圧迫する問題の修正が含まれているので、基本的にアップデートを行っておくべきでしょうね。

Web API 5.2.2 も新機能の追加やバグ修正は含まれていないですが、Json.NET への依存関係がバージョン 6.0.4 以上に変更されたので、Json.NET の新機能を使えるようになったみたいです。

Microsoft.AspNet.WebAPI 5.2.2

In this release we have made a dependency change for Json.Net 6.0.4. For more information on what is new in this release of Json.NET, see Json.NET 6.0 Release 4 - JSON Merge, Dependency Injection. This release doesn’t have any other new features or bug fixes in Web API. We have subsequently updated all other dependent packages we own to depend on this new version of Web API.

What's New in ASP.NET Web API 2.2 | The ASP.NET Site

そして今回のリリースと関係ないですが、Web API 向けに OData 5.3 RC がリリースされています。

Announcing the Release Candidates for Web API OData 5.3 - .NET Web Development and Tools Blog - Site Home - MSDN Blogs

次の MVC / Web API は 5.3 に、Web Pages は 3.3 となる予定のようです。

ASP.NET MVC 5 で追加された属性ベースのルーティングを使ってモデルバインダ絡みのエラーを回避する

ASP.NET MVC を利用した開発を行っている人は、下のエラー画面を 1 度は見たことがあるかと思います。

f:id:shiba-yan:20140826173411p:plain

MVC では URL ルーティングで定義されたパラメータやクエリ文字列を、モデルバインダが適切な型へ変換してアクションの引数へバインドする仕組みになっています。この時にアクションの引数の型が int なのに null が渡されたり、アルファベットなどが指定された場合は変換に失敗しエラーとなります。

具体的に以下のアクションを例に見ていきます。

public class ArticleController : Controller
{
    public ActionResult Details(int id)
    {
        return View();
    }
}

この場合 Details アクションは int 型の id という名前の引数を取るので、アクセスする URL としては /article/details/123 という形式である必要があります。

しかし /article/details/ や /article/details/abc といったような URL でアクセスされた場合には int への変換が不可能なので、モデルバインダから例外が投げられ HTTP エラーになるという仕組みです。URL ルーティングの定義を変更し、id というパラメータが数字のみ受け付けるように正規表現で制約をかけることも出来ますが、アクションが多い場合には非常に手間がかかります。

routes.MapRoute(
    "Article",
    "article/details/{id}",
    new { controller = "Article", action = "Index" },
    new { id = @"^[0-9]+$"}
);

このようなルーティング定義をアクションごとに手動で定義していくのはとても大変ですし、間違いも多くなってしまいそうですね。しかしアクション側としては必要な型やフォーマットが決まっているので、不正な URL の場合には 404 として扱ってほしいです。

なので、もっと楽に盤石な URL ルーティングを定義するためにも、MVC 5 から追加された属性ベースのルーティングを使って定義していきたいと思います。既に MSDN ブログでとても詳細な使い方が紹介されているので、まずはこの記事を一通り読んでおいてください。

Attribute Routing in ASP.NET MVC 5 - .NET Web Development and Tools Blog - Site Home - MSDN Blogs

割と今更感のある MVC 5 ネタですが、仕事でこのエラーがちょいちょい発生してとてもイラついたのでブログを書くことにしました。

属性ベースのルーティングは標準の URL ルーティングよりも引数へ簡単に制約をつけることが出来ます。以下の例で使っているように {articleId:int} と書くと int しか受け付けなくなります。

[RoutePrefix("article")]
public class ArticleController : Controller
{
    [Route("{articleId:int}")]
    public ActionResult Details(int articleId)
    {
        return View();
    }

    [Route]
    public ActionResult Add()
    {
        return View();
    }

    [Route("{articleId:int}/edit")]
    public ActionResult Edit(int articleId)
    {
        return View();
    }

    [HttpPost]
    [Route("{articleId:int}/edit")]
    public ActionResult Edit(int articleId, FormCollection collection)
    {
        return View();
    }
}

注意点としては、出来るだけ id といった同じ名前のパラメータを使いまわさないことと、GET と POST で同じアクション名のメソッドを定義している場合には、両方ともに Route 属性を付ける必要がある点です。属性ベースのルーティングでは、内部的に ActionDescriptor を保持しているので、同じ URL テンプレートだからと言って使い回しが効くわけではありません。

このルーティング定義で以下のような URL が有効になります。そして、それ以外の URL でアクセスされた場合には 404 になります。

  • /article/123
  • /article/add
  • /article/123/edit

試しに int が必要なパラメータをアルファベットに変更してアクセスしてみます。

f:id:shiba-yan:20140826224459p:plain

アプリケーションエラーとならずに、意図したとおり 404 として正常に処理が完了しました。

先ほどの例では、全てのアクションに Route 属性を付けていましたが、特別な URL テンプレートの指定が必要ない場合にはコントローラレベルで属性を付けることで省略が可能です。

[Route("{action}"), RoutePrefix("admin")]
public class AdminController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Settings()
    {
        return View();
    }
}

属性ベースのルーティングは自動的に URL ルーティングを組み立てているだけなので、Route 属性の URL テンプレートを空にしてしまうとアクション全てへアクセスできなくなるので注意。

ASP.NET MVC 5.2 で Facebook アプリテンプレートが使えない問題と対応法

Visual Studio 2013 Update 1 でも ASP.NET プロジェクトを作成する時に、Facebook を選択すると Facebook アプリのテンプレートが作られます。

f:id:shiba-yan:20140729185324p:plain

しかし、このテンプレートに対して MVC 5.2 などにアップデートしようとすると、Microsoft.AspNet.Mvc.Facebook の依存関係によってアップデートに失敗してしまいます。

f:id:shiba-yan:20140729185350p:plain

ぶっちゃけパッケージメタデータだけの問題で、互換性には問題ないはずなので対応が行われると思っていましたが、ちょっと前に以下のような記事が投稿されました。

Updating the MVC Facebook API - .NET Web Development and Tools Blog - Site Home - MSDN Blogs

要約すると Facebook の Graph API 2.0 対応を行うためのアップデートが行われたようです。Graph API 2.0 に対応した関係上、今までのアプリケーションとは互換性が無くなっています。

ASP.NET Facebook を使う

残念ながら Microsoft.AspNet.Mvc.Facebook は更新が行われないようなので、Graph API 2.0 で問題ない場合には Microsoft ASP.NET Facebook こと Microsoft.AspNet.Facebook という新しいパッケージを使うようにして対応しましょう。

NuGet で以下のコマンドを打ち込めば、新しいパッケージをインストールすることが出来ます。

Uninstall-Package Microsoft.AspNet.Mvc.Facebook.ja
Uninstall-Package Microsoft.AspNet.Mvc.Facebook

Install-Package Microsoft.AspNet.Facebook

新しいパッケージは今までとは異なり、バージョンが 1.0.0 となっているので少しだけ注意。

f:id:shiba-yan:20140729185411p:plain

あと、名前空間が新しくなっているので、そのままビルドするとエラーになります。

f:id:shiba-yan:20140729185418p:plain

名前空間の変更点は Mvc が無くなっただけなので、一括置換などで対応が可能だと思います。

//using Microsoft.AspNet.Mvc.Facebook;
using Microsoft.AspNet.Facebook;

これで問題ない気がしますが、これからは Graph API の仕様変更と戦う必要があります。例えば 2.0 では友達リストを取得するためには user_friends 権限を追加する必要があるので、アクションの修正を行います。

// user_friends を追加する
[FacebookAuthorize("email", "user_photos", "user_friends")]
public async Task<ActionResult> Index(FacebookContext context)
{
    if (ModelState.IsValid)
    {
        var user = await context.Client.GetCurrentUserAsync<MyAppUser>();
        return View(user);
    }

    return View("Error");
}

とりあえずはこれでユーザーの情報や写真が取れるようになります。

f:id:shiba-yan:20140729231210p:plain

しかし、友達一覧が出ていないのでアプリとして破綻気味ですね。残念なことに Graph API 2.0 では、このアプリに参加している友達の情報しか取れなくなったようなので空っぽになっています。

ASP.NET Facebook パッケージよりも Graph API 2.0 の変更内容の方が致命的なので、既存のアプリケーションは MVC 5.2 にアップデートするのは困難な気がしました。*1

*1:メタデータ弄って依存関係を変えてしまうという裏技は有り

ASP.NET Web Pages 3.2.1 のベータ版がリリースされました

最近は ASP.NET MVC 5.2 / Web API 2.2 / Web Pages 3.2 がリリースされました - しばやん雑記 でも書いたように Web Pages の更新がほぼ無いので、このまま vNext に進むのかと思っていたら 3.2.1 のベータで久しぶりに大きなアップデートが行われたみたいです。

知ったきっかけは中の人のツイートです。

そして ASP.NET 公式サイトに投稿された情報が以下の URL にあります。

What's New in ASP.NET Web Pages 3.2.1 Beta | The ASP.NET Site

要約すると Razor で大規模なコンテンツをレンダリングする時に LOH を圧迫していたのを、MSN チームと連携して改善したという話のようです。内容としては Gen 2 GC が大幅に削減されたことで、GC による停止時間が少なくなり、パフォーマンスが改善されたという流れですね。

Razor が LOH を沢山使う問題に関しては 2 年前から指摘されていたようです。

ASP.NET MVC / Web API / Web Pages - View Issue #585: Large responses end up on LOH when using Razor engine

この時は .NET 4.5 で GC 周り改善されるということで一旦閉じられたようですが、今月になって根本的な対応が CodePlex にコミットされました。

https://aspnetwebstack.codeplex.com/SourceControl/changeset/3fe0d348f00864e4f1eeaefbd027ea965787b892

コードを見てみると、今まではレンダリング結果が入っている TextWriter を ToString して中身を取り出していたようですが、コンテンツ次第では 85000 バイトを軽々超えてしまうことも考えられますね。そこで、今回は StringWriter の拡張メソッドとして CopyTo を追加し、内部では 1KB ごとに出力先の TextWriter へ書きこむように変更したようです。*1

とりあえず、実際に動いているサービスに 3.2.1-beta を入れてマルチバイト周りなどで問題が無いか見ていますが、今のところ特に問題なく使えています。

*1:char[1024] って 1KB ではない気もするけど

ASP.NET と IIS の HTTP エラー処理を理解する

以下のような ASP.NET アプリケーションのエラー画面を見たことない人は、ASP.NET 開発者にはいないと思います。この画面を本番環境では出すわけに行かないので普通はカスタマイズしますよね。

f:id:shiba-yan:20140703233722p:plain

しかし、ASP.NET では 404 をアプリケーション的に返しているのに、IIS のデフォルト 404 ページが表示されてしまい、悩まされた経験がある人も多いかと思います。これは ASP.NET と IIS で扱いが異なっているのが原因で、設定も別々に存在しているのでまとめておきたいと思います。

ASP.NET

ASP.NET では Web.config の system.web 要素内に customError 要素を追加して、ステータスコード別にリダイレクト先の URL を設定できるようになります。

customErrors 要素 (ASP.NET 設定スキーマ)

例えば 404 ページをカスタマイズしたい場合には、以下のような定義を追加します。

<system.web>
  <customErrors mode="On">
    <error statusCode="404" redirect="/home/error"/>
  </customErrors>
</system.web>

これで 404 エラーの場合には /home/error にリダイレクトされますが、この customError 要素での設定が有効になるのは ASP.NET のパイプライン内部だけです。

f:id:shiba-yan:20140703233922p:plain

ASP.NET MVC などルーティングを使っている場合には、静的ファイルを除く全てのアクセスが ASP.NET のパイプラインで処理されるので問題ないですが、ASP.NET 内で 404 を返した場合には意図したとおりに動作しなくなります。

例えば、以下のようにアクション内で HttpNotFound メソッドを使った場合ですね。

public ActionResult NotFound()
{
    return HttpNotFound();
}

この場合は ASP.NET パイプラインの最後で 404 にセットされるので、customError 要素での設定が無効になってしまいます。なので、IIS 側の設定も同時に行っておく必要があります。

IIS

当然ですが IIS はルーティングが使われていない、静的なコンテンツを扱う場合にリクエストされたファイルが存在しなければ 404 を返します。この挙動をカスタマイズするための機能がもちろん用意されています。

それが IIS 7 から追加された system.webServer 要素に定義できる httpErrors 要素です。

HTTP Errors <httpErrors> : The Official Microsoft IIS Site

errorMode を Custom にすることで自由に HTTP ステータスコード毎の挙動をカスタマイズできます。例えば、先ほどの ASP.NET と同じように 404 を返した時に /home/error へリダイレクトさせる設定は以下のようになります。

<system.webServer>
  <httpErrors errorMode="Custom">
    <remove statusCode="404" />
    <error statusCode="404" path="/home/error" responseMode="Redirect" />
  </httpErrors>
</system.webServer>

error 要素を追加する前に remove 要素を使って、デフォルトの設定を一応削除しておきます。設定自体は殆ど同じですが、responseMode を ExecuteURL にすることで現在の URL から移動せずにエラーページを表示することも出来ます。

ASP.NET アプリケーションで HTTP エラー画面をカスタマイズする場合には、この 2 つの設定を同時にしておくのが安全ですね。

おまけ:TaaS メソッド

実は IIS の httpErrors を知ったきっかけは、田口さんの ARR を使って 3 秒ぐらいでレスポンスを返すというテクニックでした。

Azure で ARR を使って3秒程度でレスポンスを返す | たんたか

この方法を使うと、リバースプロキシ側がタイムアウトした時に 502 が返ってしまうのですが、httpErrors を使って 502 に対応する設定を用意しておけば、どんな時でも 200 を返すことが出来るらしいです。

なので、この ARR のタイムアウトと httpErrors を使う方法を TaaS メソッドと呼んでます。

ASP.NET MVC 5.2 / Web API 2.2 / Web Pages 3.2 がリリースされました

個人的には ASP.NET vNext のほうが気になって仕方ないですが、ひっそりと ASP.NET MVC 5.2 や Web API 2.2 がリリースされてました。

NuGet Gallery | Microsoft ASP.NET MVC 5.2.0
NuGet Gallery | Microsoft ASP.NET Web API 2.2 5.2.0
NuGet Gallery | Microsoft ASP.NET Web Pages 3.2.0

Visual Studio 2013 Update 1 をインストール済みの環境であれば、NuGet でパッケージを一括更新するだけで問題ないはずです。最近はあまり更新がありませんが、一つずつ見ていくことにしましょう。

MVC 5.2

What’s New in ASP.NET MVC 5.2 | The ASP.NET Site

リリースノートは最近あまり書かず、殆どは CodePlex の Issue を見てくれと言うスタンスのようです。

気になる修正を挙げると、Knockout.js と ASP.NET MVC の組み合わせ時にモデルバインディングが腐る件 - しばやん雑記 で書いた jQuery のシリアライズ形式を MVC のモデルバインダが受け付けてくれないという件は、この 5.2 で修正されたみたいです。

ASP.NET MVC / Web API / Web Pages - View Issue #1564: FormValueProvider does not recognize array item members from jQuery posts

他にも SelectList / DropDownList 周りでグループや無効化への対応が追加されていたりしてます。

f:id:shiba-yan:20140702175128p:plain

SelectList のオーバーロードが倍ぐらいに増えています。

public class Master
{
    public static readonly List<Prefecture> Prefectures = new List<Prefecture>
    {
        new Prefecture { Id = 1, Name = "青森", Region = "東北" },
        new Prefecture { Id = 2, Name = "東京", Region = "関東" },
        new Prefecture { Id = 3, Name = "愛知", Region = "中部" },
        new Prefecture { Id = 4, Name = "大阪", Region = "関西" },
        new Prefecture { Id = 5, Name = "愛媛", Region = "四国" },
        new Prefecture { Id = 6, Name = "福岡", Region = "九州" },
    };

    public static readonly SelectList PrefectureList = new SelectList(Prefectures, "Id", "Name", "Region", (object)null);

    public class Prefecture
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Region { get; set; }
    }
}

こんなモデルを用意して、普通に DropDownList/DropDownListFor で使うと optgroup を使った形でレンダリングが行われます。

f:id:shiba-yan:20140702182021p:plain

あと、Remote 属性を特定のエリア内で使った場合、ルートに用意されたアクションを実行出来ない件も修正されました。

public class FormModel
{
    // /AreaName/User/Exists を見に行く
    [Remote("Exists", "User")
    public string UserId1 { get; set; }

    // /User/Exists を見に行く
    [Remote("Exists", "User", AreaReference.UseRoot)
    public string UserId2 { get; set; }
}

Remote 属性のプロパティして AreaReference という enum が追加されたので、これを使って現在のエリアかルートなのかを指定できます。

Web API 2.2

What's New in ASP.NET Web API 2.2 | The ASP.NET Site

Web API 2.2 では OData v4 の対応とクライアントライブラリの Windows Phone 8.1 対応が目玉っぽいです。

いつも通りですが Web API は関連する Issue が多すぎるので、詳しい人に任せたいと思います。

ASP.NET MVC / Web API / Web Pages - Issues

元々、自分はあまり Web API には詳しくないので(

Web Pages 3.2

What's New in ASP.NET Web Pages 3.2 | The ASP.NET Site

Web Pages に関しては、特に目立った更新内容が無さそうです。強いてあげれば、エラーメッセージが一部わかりやすくなったみたいですね。

ASP.NET MVC / Web API / Web Pages - View Issue #1838: Razor - Cannot trigger content mode/need better error message

Knockout.js と ASP.NET MVC の組み合わせ時にモデルバインディングが腐る件

今日も Knockout.js を使って ASP.NET MVC に POST するようなコードを書いていたのですが、モデルバインダが意図したとおりに値をバインドしてくれない時がありました。

まずは ASP.NET MVC 側のコードを見ていきましょう。難しいことはしてません。

public class ChildFormModel
{
    public string Name { get; set; }
}

public class FormModel
{
    public string Name { get; set; }
    public List<ChildFormModel> Items { get; set; }
}

[HttpPost]
public ActionResult Post(FormModel model)
{
    return Json(true);
}

簡単にコードを紹介すると、親のモデルがさらにクラスのリストを持っている形です。

それでは次は Knockout.js 側のコードを見ていきます。

var viewModel = {
    Name: ko.observable("foo"),
    Items: ko.observableArray([
        { Name: ko.observable("bar") }
    ])
};

function saveChange() {
    $.ajax({
        type: "POST",
        url: "/Home/Post",
        data: ko.toJS(viewModel)
    });
}

めんどくさかったので初期データを横着しました。ちなみに Knockout.js の Observable なモデルを普通の JavaScript オブジェクトにするためには ko.toJS メソッドを使います。

これで実際に saveChange メソッドを実行すると、Visual Studio 側でネストされた ChildFormModel の Name プロパティに値がセットされていないことがわかります。

f:id:shiba-yan:20140625211118p:plain

何故動作しないかを調べたら、POST されたデータが以下のようになっていました。

Name:foo
Items[0][Name]:bar

ASP.NET MVC のモデルバインダは Items[0].Name という形式でないと正しくバインディング出来ないようになっているので、jQuery が内部的に変換するために使っているメソッドが原因といえます。

どうやって回避するかですが、わざわざ application/x-www-form-urlencoded 形式でエンコードしなくても、ASP.NET MVC は JSON からもバインディング出来るので、難しく考えずに JSON で送ってしまいます。

var viewModel = {
    Name: ko.observable("foo"),
    Items: ko.observableArray([
        { Name: ko.observable("bar") }
    ])
};

function saveChange() {
    $.ajax({
        type: "POST",
        url: "/Home/Post",
        contentType: "application/json",
        data: ko.toJSON(viewModel)
    });
}

Knockout.js には ko.toJSON という Observable なモデルを JSON にしてくれるメソッドがあるので、簡単に JSON を送るように変更できます。

f:id:shiba-yan:20140625212624p:plain

実際に動かしてみると、今度はちゃんと値がセットされていることがわかります。モデルが複雑な場合には JSON を使うのが無難ですね。