ASP.NET 4.5 の Web フォームの新機能
ASP.NET Web Forms の新しいバージョンでは、データを操作する際のユーザー エクスペリエンスの向上に重点を置いた多くの機能強化が導入されています。
以前のバージョンの Web Forms では、データ バインディングを使ってオブジェクト メンバーの値を出力するときに、データ バインディング式 Bind() または Eval() を使いました。 新しいバージョンの ASP.NET では、新しい ItemType プロパティを使って、コントロールがバインドされるデータの型を宣言できます。 このプロパティを設定すると、厳密に型指定された変数を使って、IntelliSense、メンバーのナビゲーション、コンパイル時のチェックなど、Visual Studio 開発エクスペリエンスのすべての恩恵を受けることができます。
データ バインドされたコントロールにより、データの選択、更新、削除、挿入用に独自のカスタム メソッドを指定することもできるようになりました。これにより、ページ コントロールとアプリケーション ロジック間の対話が簡素化されます。 さらに、モデル バインド機能が ASP.NET に追加されました。つまり、ページからメソッドの型パラメーターに直接データをマップできます。
最新バージョンの Web Forms では、ユーザーによる入力の検証も簡単になります。 System.ComponentModel.DataAnnotations 名前空間の検証属性を使ってモデル クラスに注釈を付け、すべてのサイト コントロールがその情報を使ってユーザーによる入力を検証することを要求できるようになりました。 Web Forms のクライアント側検証が jQuery と統合され、わかりやすいクライアント側コードと控えめな JavaScript 機能が提供されるようになりました。
要求の検証に関する領域では、アプリケーションの特定の部分で要求の検証を選択的に無効にしたり、無効な要求データを読み取ったりしやすくするための機能強化が行われました。
HTML5 の新機能を利用するために、Web Forms サーバー コントロールにいくつかの機能強化が行われています。
- TextBox コントロールの TextMode プロパティは、email、datetime などの HTML5 の新しい入力の種類をサポートするように更新されました。
- FileUpload コントロールで、HTML5 の複数ファイルのアップロード機能をサポートするブラウザーからの複数ファイルのアップロードがサポートされるようになりました。
- 検証コントロールで HTML5 入力要素の検証がサポートされるようになりました。
- URL を表す属性を持つ新しい HTML5 要素で、runat="server" がサポートされるようになりました。 そのため、URL パスで ~ 演算子などの ASP.NET 規則を使ってアプリケーション ルートを表すことができます (例: <video runat="server" src="~/myVideo.wmv"></video>)。
- UpdatePanel コントロールは、HTML5 入力フィールドのポストをサポートするように修正されました。
公式の ASP.NET ポータルでは、ASP.NET Web Forms 4.5 の新機能の例をさらに確認できます: ASP.NET 4.5 と Visual Studio 2012 の新機能
すべてのサンプル コードとスニペットは、Web Camps トレーニング キットに含まれています。
目標
このハンズオン ラボでは、次のことを行う方法について説明します。
- 厳密に型指定されたデータ バインディング式を使う
- Web Forms の新しいモデル バインド機能を使う
- ページ データを分離コード メソッドにマップするために値プロバイダーを使う
- ユーザーによる入力の検証にデータ注釈を使う
- Web Forms の jQuery を使った控えめなクライアント側検証を利用する
- きめ細かい要求の検証を実装する
- Web Forms で非同期ページ処理を実装する
前提条件
このラボを完了するには、次の項目が必要です:
- Microsoft Visual Studio Express 2012 for Web 以降 (インストール方法については「付録 A」を参照してください)。
段取り
コード スニペットのインストール
利便性のため、このラボで管理するコードの多くは、Visual Studio コード スニペットとして入手可能です。 コード スニペットをインストールするには、.\Source\Setup\CodeSnippets.vsi ファイルを実行します。
Visual Studio Code スニペットに慣れていない場合は、その使用方法について、このドキュメントの付録「付録 C: コード スニペットの使用」を参照してください。
演習
このハンズオン ラボに含まれる演習は次のとおりです。
Note
各演習には、演習を完了した後に得られる、結果のソリューションが含まれる End フォルダーが付帯しています。 このソリューションは、演習の作業についてさらにヘルプが必要な場合に、ガイドとして使用できます。
このラボの推定所要時間: 60 分。
演習 1: ASP.NET Web Forms でのモデル バインド
ASP.NET Web Forms の新しいバージョンでは、データを操作する際のエクスペリエンスの向上に重点を置いた多くの機能強化が導入されています。 この演習では、厳密に型指定されたデータ コントロールとモデル バインドについて学習します。
タスク 1 - 厳密に型指定されたデータ バインディングの使用
このタスクでは、ASP.NET 4.5 で使用できる厳密に型指定された新しいバインドについて調べます。
Source/Ex1-ModelBinding/Begin/ フォルダーにある Begin ソリューションを開きます。
作業の続行前に、いくつかの不足している NuGet パッケージをダウンロードする必要があります。 これを行うには、[プロジェクト] メニューをクリックし、[NuGet パッケージの管理] を選択します。
[NuGet パッケージの管理] ダイアログで、[復元] をクリックして、不足しているパッケージをダウンロードします。
最後に、[ビルド] | [ソリューションのビルド] をクリックしてソリューションをビルドします。
Note
NuGet を使用する利点の 1 つは、プロジェクト内のすべてのライブラリを発送する必要がなく、プロジェクト サイズが縮小される点です。 NuGet Power Tools では、Packages.config ファイルでパッケージのバージョンを指定することで、プロジェクトを初回実行するときに必要なすべてのライブラリをダウンロードできます。 このラボから既存のソリューションを開いた後に、これらの手順を実行する必要があるのは、このような理由によります。
Customers.aspx ページを開きます。 メイン コントロールに番号なしリストを配置し、その内部に各顧客を一覧表示するための Repeater コントロールを含めます。 以下のコードに示すように、Repeater の名前を customersRepeater に設定します。
以前のバージョンの Web Forms では、データ バインディングを使ってデータ バインディング先であるオブジェクトのメンバーの値を出力する場合、Eval メソッドの呼び出しとともにデータ バインディング式を使って、メンバーの名前を文字列として渡していました。
実行時に、Eval に対するこれらの呼び出しでは現在バインドされているオブジェクトに対するリフレクションが使われ、名前を指定したメンバーの値が読み取られ、結果が HTML で表示されます。 この方法を使うと、任意の整形されていないデータに対して、非常に簡単にデータ バインドを行うことができます。
残念ながら、Visual Studio の優れた開発時エクスペリエンス機能の多くは失われます。たとえば、メンバー名に関する IntelliSense、ナビゲーションのサポート ([定義へ移動] など)、コンパイル時のチェックなどです。
... <asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server"> <h3>Customers</h3> <ul> <asp:Repeater ID="customersRepeater" runat="server"> <ItemTemplate> <li> <%# Eval("FirstName") %> <%# Eval("LastName") %> </li> </ItemTemplate> </asp:Repeater> </ul> <a href="CustomerDetails.aspx">Add a New Customer</a> </asp:Content>
Customers.aspx.cs ページを開きます。
次の using ステートメントを追加します。
using System.Linq;
Page_Load メソッドに、Repeater に顧客の一覧を設定するコードを追加します。
(コード スニペット - "Web Forms ラボ - 演習 1 - 顧客データ ソースのバインド")
protected void Page_Load(object sender, EventArgs e) { using (var db = new WebFormsLab.Model.ProductsContext()) { this.customersRepeater.DataSource = db.Customers.ToList(); this.customersRepeater.DataBind(); } }
このソリューションでは、EntityFramework を CodeFirst とともに使用して、データベースの作成とアクセスを行います。 以下のコードで、customersRepeater はデータベースのすべての顧客を返す具体化されたクエリにバインドされます。
F5 キーを押してソリューションを実行し、Customers ページに移動して、Repeater の動作を確認します。 ソリューションでは CodeFirst を使っているので、アプリケーションの実行時にローカルの SQL Express インスタンスでデータベースが作成され、設定されます。
"Repeater で顧客を一覧表示する"
Note
Visual Studio 2012 では、IIS Express が既定の Web 開発サーバーです。
ブラウザーを閉じて、Visual Studio に戻ります。
次に、厳密に型指定されたバインドを使うように実装を置き換えます。 Customers.aspx ページを開き、Repeater で新しい ItemType 属性を使って、Customer 型をバインドの型として設定します。
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <ul> <asp:Repeater ID="customersRepeater" ItemType="WebFormsLab.Model.Customer" runat="server"> <ItemTemplate> ... </ItemTemplate> </asp:Repeater> </ul> <a href="CustomerDetails.aspx">Add a New Customer</a> </asp:Content>
ItemType プロパティを使うと、コントロールがバインドされるデータの型を宣言でき、データ バインドされたコントロール内で厳密に型指定されたバインドを使うことができます。
ItemTemplate の内容を次のコードに置き換えます。
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> ... <ul> <asp:Repeater ID="customersRepeater" ItemType="WebFormsLab.Model.Customer" runat="server"> <ItemTemplate> <li> <a href="CustomerDetails.aspx?id=<%#: Item.Id %>"> <%#: Item.FirstName %> <%#: Item.LastName %> </a> </li> </ItemTemplate> </asp:Repeater> </ul> <a href="CustomerDetails.aspx">Add a New Customer</a> </asp:Content>
上記の方法には、Eval() と Bind() の呼び出しが遅延バインディングされるという欠点があります。つまり、プロパティ名を表す文字列を渡します。 これは、メンバー名に関する IntelliSense、コード ナビゲーションのサポート ([定義へ移動] など)、コンパイル時のチェックのサポートが利用できないことを意味します。
ItemType プロパティを設定すると、2 つの新しい型指定された変数がデータ バインディング式のスコープで生成されます (Item と BindItem)。 データ バインディング式でこれらの厳密に型指定された変数を使い、Visual Studio 開発エクスペリエンスのすべての恩恵を受けることができます。
式で使われている ": " によって、出力が自動的に HTML エンコードされ、セキュリティ上の問題を回避できます (クロスサイト スクリプティング攻撃など)。 この表記は、応答の書き込みでは .NET 4 から使用できましたが、データ バインディング式でも使用できるようになりました。
Note
Item メンバーは、一方向のバインド用に機能します。 両方向のバインドを実行したい場合は、BindItem メンバーを使用します。
"厳密に型指定されたバインドでの IntelliSense のサポート"
F5 キーを押してソリューションを実行し、Customers ページに移動して、変更内容が期待どおりに動作することを確認します。
"顧客の詳細の一覧表示"
ブラウザーを閉じて、Visual Studio に戻ります。
タスク 2 - Web Forms へのモデル バインドの導入
以前のバージョンの ASP.NET Web Forms では、両方向のデータ バインディング (データの取得と更新の両方) を実行したい場合は、データ ソース オブジェクトを使う必要がありました。 たとえば、オブジェクト データ ソース、SQL データ ソース、LINQ データ ソースなどを使いました。 ただし、データを処理するためにカスタム コードが必要なシナリオでは、オブジェクト データ ソースを使う必要があったため、いくつかの欠点が生じていました。 たとえば、複合型を回避する必要や、検証ロジックを実行するときに例外を処理する必要がありました。
新しいバージョンの ASP.NET Web Forms では、データ バインドされたコントロールでモデル バインドがサポートされます。 つまり、選択、更新、挿入、削除のメソッドをデータ バインドされたコントロールで直接指定し、分離コード ファイルまたは別のクラスからロジックを呼び出すことができます。
これについて説明するために、GridView と新しい SelectMethod 属性を使って製品カテゴリを一覧表示します。 この属性を使うと、GridView のデータを取得するためのメソッドを指定できます。
Products.aspx ページを開き、GridView を追加します。 次のように、厳密に型指定されたバインドを使い、並べ替えとページングを有効にするように GridView を構成します。
<asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server"> <asp:GridView ID="categoriesGrid" runat="server" AutoGenerateColumns="false" ItemType="WebFormsLab.Model.Category" DataKeyNames="CategoryID"> <Columns> <asp:BoundField DataField="CategoryId" HeaderText="ID" SortExpression="CategoryId" /> <asp:BoundField DataField="CategoryName" HeaderText="Name" SortExpression="CategoryName" /> <asp:BoundField DataField="Description" HeaderText="Description" /> <asp:TemplateField HeaderText="# of Products"> <ItemTemplate><%#: Item.Products.Count %></ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView> </asp:Content>
新しい SelectMethod 属性を使って、GetCategories メソッドを呼び出してデータを選択するように GridView を構成します。
<asp:GridView ID="categoriesGrid" runat="server" AutoGenerateColumns="false" ItemType="WebFormsLab.Model.Category" DataKeyNames="CategoryId" SelectMethod="GetCategories"> <Columns> <asp:BoundField DataField="CategoryId" HeaderText="ID" SortExpression="CategoryId" /> <asp:BoundField DataField="CategoryName" HeaderText="Name" SortExpression="CategoryName" /> <asp:BoundField DataField="Description" HeaderText="Description" /> <asp:TemplateField HeaderText="# of Products"> <ItemTemplate><%#: Item.Products.Count %></ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView>
Products.aspx.cs 分離コード ファイルを開き、次の using ステートメントを追加します。
(コード スニペット - "Web Forms ラボ - 演習 1 - 名前空間")
using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; using WebFormsLab.Model;
Products クラスにプライベート メンバーを追加し、ProductsContext の新しいインスタンスを代入します。 このプロパティには、データベースへの接続を可能にする Entity Framework データ コンテキストが格納されます。
public partial class Products : System.Web.UI.Page { private ProductsContext db = new ProductsContext(); ...
LINQ を使ってカテゴリの一覧を取得する GetCategories メソッドを作成します。 クエリには Products プロパティを含めるので、GridView では各カテゴリの製品の量を表示できます。 このメソッドが、ページ ライフサイクルにおいて後ほど実行されるクエリを表す生の IQueryable オブジェクトを返している点に注意してください。
(コード スニペット - "Web Forms ラボ - 演習 1 - GetCategories")
public IQueryable<Category> GetCategories() { var query = this.db.Categories .Include(c => c.Products); return query; }
Note
以前のバージョンの ASP.NET Web Forms では、オブジェクト データ ソースのコンテキスト内で独自のリポジトリ ロジックを使って並べ替えとページングを有効にするには、独自のカスタム コードを記述し、必要なすべてのパラメーターを受け取る必要がありました。 現在は、データ バインディング メソッドで IQueryable を返すことができ、これはまだ実行されないクエリを表すので、ASP.NET がクエリを変更して適切な並べ替えとページングのパラメーターを追加することができます。
F5 キーを押してサイトのデバッグを開始し、Products ページに移動します。 GetCategories メソッドから返されたカテゴリによって GridView が設定されていることがわかります。
"モデル バインドを使った GridView の設定"
Shift+F5 キーを押してデバッグを停止します。
タスク 3 - モデル バインドの値プロバイダー
モデル バインドを使うと、データ バインドされたコントロールで直接カスタム メソッドを指定してデータを操作できるだけでなく、ページからこれらのメソッドのパラメーターにデータをマップすることもできます。 メソッド パラメーターでは、値プロバイダー属性を使って値のデータ ソースを指定できます。 次に例を示します。
- ページ上のコントロール
- クエリ文字列値
- データの表示
- セッションの状態
- Cookie
- ポストされたフォームのデータ
- ビュー状態
- カスタムの値プロバイダーもサポートされています
MVC 4 ASP.NET を使ったことがある場合は、モデル バインドのサポートが似ていることがわかります。 実際、これらの機能は ASP.NET MVC に由来し、Web Forms でも使用できるように System.Web アセンブリに移動されたものです。
このタスクでは、GridView を更新して、各カテゴリの製品の量で結果をフィルター処理し、モデル バインドを使ってフィルター パラメーターを受け取ります。
Products.aspx ページに戻ります。
次に示すように、GridView の上部に、各カテゴリの製品数を選択する Label と ComboBox を追加します。
<h3>Categories</h3> <asp:Label ID="Label1" runat="server" AssociatedControlID="minProductsCount"> Show categories with at least this number of products: </asp:Label> <asp:DropDownList runat="server" ID="minProductsCount" AutoPostBack="true"> <asp:ListItem Value="" Text="-" /> <asp:ListItem Text="1" /> <asp:ListItem Text="3" /> <asp:ListItem Text="5" /> </asp:DropDownList> <br/>
GridView に EmptyDataTemplate を追加して、選択した製品数のカテゴリがない場合にメッセージを表示します。
<asp:GridView ID="categoriesGrid" runat="server" AutoGenerateColumns="false" ItemType="WebFormsLab.Model.Category" DataKeyNames="CategoryId" SelectMethod="GetCategories"> <Columns> <asp:BoundField DataField="CategoryId" HeaderText="ID" /> <asp:BoundField DataField="CategoryName" HeaderText="Name" /> <asp:BoundField DataField="Description" HeaderText="Description" /> <asp:TemplateField HeaderText="# of Products"> <ItemTemplate><%#: Item.Products.Count %></ItemTemplate> </asp:TemplateField> </Columns> <EmptyDataTemplate> No categories found with a product count of <%#: minProductsCount.SelectedValue %> </EmptyDataTemplate> </asp:GridView>
Products.aspx.cs 分離コードを開き、次の using ステートメントを追加します。
using System.Web.ModelBinding;
整数の minProductsCount 引数を受け取って返された結果をフィルター処理するように、GetCategories メソッドを変更します。 そのためには、メソッドを次のコードに置き換えます。
(コード スニペット - "Web Forms ラボ - 演習 1 - GetCategories 2")
public IQueryable<Category> GetCategories([Control]int? minProductsCount) { var query = this.db.Categories .Include(c => c.Products); if (minProductsCount.HasValue) { query = query.Where(c => c.Products.Count >= minProductsCount); } return query; }
minProductsCount 引数の新しい [Control] 属性により、その値はページ内のコントロールを使って設定する必要があることが ASP.NET に伝えられます。 ASP.NET は、引数の名前 (minProductsCount) に一致するコントロールを探し、必要なマッピングと変換を実行して、パラメーターにコントロールの値を入力します。
または、この属性によって、値を取得するコントロールを指定できるオーバーロードされたコンストラクターを提供します。
Note
データ バインディング機能の 1 つの目標は、ページ操作のために記述する必要があるコードの量を減らすことです。 [Control] 値プロバイダーとは別に、メソッド パラメーターで他のモデル バインド プロバイダーも使用できます。 その一部はこのタスクのはじめに記載されています。
F5 キーを押してサイトのデバッグを開始し、Products ページに移動します。 ドロップダウン リストで製品数を選択し、GridView がどのように更新されるかを確認します。
"ドロップダウン リストの値を使った GridView のフィルター処理"
デバッグを停止します。
タスク 4 - フィルター処理にモデル バインドを使う
このタスクでは、2 つ目の子 GridView を追加して、選択したカテゴリ内の製品を表示します。
Products.aspx ページを開き、Select ボタンを自動生成するようにカテゴリの GridView を更新します。
<asp:GridView ID="categoriesGrid" runat="server" AutoGenerateColumns="false" ItemType="WebFormsLab.Model.Category" DataKeyNames="CategoryId" SelectMethod="GetCategories" AutoGenerateSelectButton="true">
productsGrid という名前の 2 つ目の GridView を下部に追加します。 ItemType を WebFormsLab.Model.Productに、DataKeyNames を ProductId に、SelectMethod をGetProducts に設定します。 AutoGenerateColumns を false に設定して、ProductId、ProductName、Description、UnitPrice の列を追加します。
<h3>Products</h3> <asp:GridView ID="productsGrid" runat="server" CellPadding="4" AutoGenerateColumns="false" ItemType="WebFormsLab.Model.Product" DataKeyNames="ProductId" SelectMethod="GetProducts"> <Columns> <asp:BoundField DataField="ProductId" HeaderText="ID" /> <asp:BoundField DataField="ProductName" HeaderText="Name" /> <asp:BoundField DataField="Description" HeaderText="Description" HtmlEncode="false" /> <asp:BoundField DataField="UnitPrice" HeaderText="Price" /> </Columns> <EmptyDataTemplate> Select a category above to see its products </EmptyDataTemplate> </asp:GridView>
Products.aspx.cs 分離コード ファイルを開きます。 カテゴリの GridView からカテゴリ ID を受け取り、製品をフィルター処理するように GetProducts メソッドを実装します。 モデル バインドにより、categoriesGrid の選択された行を使ってパラメーター値が設定されます。 引数名とコントロール名が一致しないため、Control 値プロバイダーでコントロールの名前を指定する必要があります。
(コード スニペット - "Web Forms ラボ - 演習 1 - GetProducts")
public IEnumerable<Product> GetProducts([Control("categoriesGrid")]int? categoryId) { return this.db.Products.Where(p => p.CategoryId == categoryId); }
Note
この方法では、これらのメソッドを簡単に単体テストできます。 Web Forms が実行されない単体テストのコンテキストでは、[Control] 属性によって特定のアクションが実行されることはありません。
Products.aspx ページを開き、製品の GridView を見つけます。 選択した製品を編集するためのリンクを表示するように、製品の GridView を更新します。
<h3>Products</h3> <asp:GridView ID="productsGrid" runat="server" CellPadding="4" AutoGenerateColumns="false" ItemType="WebFormsLab.Model.Product" DataKeyNames="ProductId" SelectMethod="GetProducts"> <Columns> <asp:TemplateField> <ItemTemplate> <a href="ProductDetails.aspx?productId=<%#: Item.ProductId %>">View</a> </ItemTemplate> </asp:TemplateField> <asp:BoundField DataField="ProductId" HeaderText="ID" /> <asp:BoundField DataField="ProductName" HeaderText="Name" /> <asp:BoundField DataField="Description" HeaderText="Description" HtmlEncode="false" /> <asp:BoundField DataField="UnitPrice" HeaderText="Price" /> </Columns> <EmptyDataTemplate> Select a category above to see its products </EmptyDataTemplate> </asp:GridView>
ProductDetails.aspx ページの分離コードを開き、SelectProduct メソッドを次のコードに置き換えます。
(コード スニペット - "Web Forms ラボ - 演習 1 - SelectProduct メソッド")
public Product SelectProduct([QueryString]int? productId) { return this.db.Products.Find(productId); }
Note
クエリ文字列の productId パラメーターからメソッド パラメーターを入力するために [QueryString] 属性が使われていることがわかります。
F5 キーを押してサイトのデバッグを開始し、Products ページに移動します。 カテゴリの GridView から任意のカテゴリを選択すると、製品の GridView が更新されることがわかります。
"選択したカテゴリの製品を表示する"
製品の [View] リンクをクリックして、ProductDetails.aspx ページを開きます。
このページは SelectMethod とクエリ文字列からの productId パラメーターを使って製品を取得していることに注意してください。
"製品の詳細の表示"
Note
HTML の説明を入力する機能は、次の演習で実装します。
タスク 5 - 更新操作にモデル バインドを使う
前のタスクでは、モデル バインドを主にデータの選択に使いました。このタスクでは、更新操作でモデル バインドを使う方法について説明します。
カテゴリの GridView を更新して、ユーザーがカテゴリを更新できるようにします。
Products.aspx ページを開き、カテゴリの GridView を更新して、Edit ボタンを自動生成し、新しい UpdateMethod 属性を使って選択した項目を更新する UpdateCategory メソッドを指定するようにします。
<asp:GridView ID="categoriesGrid" runat="server" AutoGenerateColumns="false" ItemType="WebFormsLab.Model.Category" DataKeyNames="CategoryId" SelectMethod="GetCategories" AutoGenerateSelectButton="true" AutoGenerateEditButton="true" UpdateMethod="UpdateCategory">
GridView の DataKeyNames 属性は、モデル バインドされたオブジェクトを一意に識別するメンバーはどれか、したがって、更新メソッドが少なくとも受け取る必要があるパラメーターはどれかを定義します。
Products.aspx.cs 分離コード ファイルを開き、UpdateCategory メソッドを実装します。 このメソッドでは、カテゴリ ID を受け取って現在のカテゴリを読み込み、GridView からの値を設定してから、カテゴリを更新する必要があります。
(コード スニペット - "Web Forms ラボ - 演習 1 - UpdateCategory")
public void UpdateCategory(int categoryId) { var category = this.db.Categories.Find(categoryId); this.TryUpdateModel(category); if (this.ModelState.IsValid) { this.db.SaveChanges(); } }
Page クラスの新しい TryUpdateModel メソッドは、ページ内のコントロールの値を使ってモデル オブジェクトを設定する役割を果たします。 このケースでは、編集されている現在の GridView の行からの更新された値が category オブジェクトに置き換えられます。
Note
次の演習では、ModelState.IsValid を使ってオブジェクトの編集時にユーザーが入力したデータを検証する方法について説明します。
サイトを実行し、Products ページに移動します。 カテゴリを編集します。 新しい名前を入力し、[Update] をクリックして変更内容を保存します。
"カテゴリの編集"
演習 2: データの入力規則
この演習では、ASP.NET 4.5 のデータの入力規則に関する新機能について説明します。 Web Forms の新しい控えめな検証機能を確認します。 アプリケーション モデル クラスでデータ注釈を使ってユーザーによる入力を検証し、最後に、ページ内の個々のコントロールに対して要求の検証を有効または無効にする方法について説明します。
タスク 1 - 控えめな検証
検証コントロールを含む複雑なデータを使用したフォームでは、ページで生成される JavaScript コードが多くなりすぎる傾向があり、コードの約 60% を占めることもあります。 控えめな検証を有効にすると、よりクリーンで整理された HTML コードが生成されるようになります。
このセクションでは、ASP.NET の控えめな検証を有効にして、両方の構成によって生成された HTML コードを比較します。
Visual Studio 2012 を開き、このラボの Source\Ex2-Validation\Begin フォルダーにある Begin ソリューションを開きます。 または、前の演習から既存のソリューションで引き続き作業することもできます。
提供された Begin ソリューションを開いた場合は、続行する前に、不足している NuGet パッケージをダウンロードする必要があります。 これを行うには、ソリューション エクスプローラーで WebFormsLab プロジェクトの [NuGet パッケージの管理] をクリックします。
[NuGet パッケージの管理] ダイアログで、[復元] をクリックして、不足しているパッケージをダウンロードします。
最後に、[ビルド] | [ソリューションのビルド] をクリックしてソリューションをビルドします。
Note
NuGet を使用する利点の 1 つは、プロジェクト内のすべてのライブラリを発送する必要がなく、プロジェクト サイズが縮小される点です。 NuGet Power Tools では、Packages.config ファイルでパッケージのバージョンを指定することで、プロジェクトを初回実行するときに必要なすべてのライブラリをダウンロードできます。 このラボから既存のソリューションを開いた後に、これらの手順を実行する必要があるのは、このような理由によります。
F5 キーを押して Web アプリケーションを起動します。 Customers ページに移動し、[Add a New Customer] リンクをクリックします。
ブラウザー ページを右クリックし、[ソースの表示] オプションを選択して、アプリケーションによって生成された HTML コードを開きます。
"ページの HTML コードを表示"
ページのソース コードをスクロールしていくと、検証を実行してエラーを一覧表示するために、ASP.NET が JavaScript コードとデータ検証コントロールをページに挿入していることがわかります。
"CustomerDetails ページの検証 JavaScript コード"
ブラウザーを閉じて、Visual Studio に戻ります。
次に、控えめな検証を有効にします。 Web.Config を開き、AppSettings セクションにある ValidationSettings:UnobtrusiveValidationMode キーを見つけます。このキーの値を WebForms に設定します。
<configuration> ... <appSettings> <add key="aspnet:uselegacysynchronizationcontext" value="false" /> <add key="ValidationSettings:UnobtrusiveValidationMode" value="WebForms"/>
Note
控えめな検証を一部のページでのみ有効にしたいときは、"Page_Load" イベントでこのプロパティを設定することもできます。
CustomerDetails.aspx を開き、F5 キーを押して Web アプリケーションを起動します。
F12 キーを押して IE 開発者ツールを開きます。 開発者ツールが開いたら、スクリプト タブを選択します。メニューから CustomerDetails.aspx を選択して、ページ上で jQuery を実行するために必要なスクリプトがローカル サイトからブラウザーに読み込まれていることに注目してください。
"ローカル IIS サーバーから jQuery JavaScript ファイルを直接読み込む"
ブラウザーを閉じて、Visual Studio に戻ります。 Site.Master ファイルをもう一度開き、ScriptManager を見つけます。 値が True の属性 EnableCdn プロパティを追加します。 これにより、jQuery がローカル サイトの URL ではなくオンラインの URL から読み込まれます。
Visual Studio で CustomerDetails.aspx を開きます。 F5 キーを押してサイトを実行します。 Internet Explorer が開いたら、F12 キーを押して開発者ツールを開きます。 [スクリプト] タブを選択して、ドロップダウン リストを確認します。 jQuery JavaScript ファイルがローカル サイトからではなく、オンラインの jQuery CDN から読み込まれていることがわかります。
"CDN からの jQuery JavaScript ファイルの読み込み"
ブラウザーの [ソースの表示] オプションを使って、もう一度 HTML ページのソース コードを開きます。 控えめな検証を有効にしたので、ASP.NET が挿入された JavaScript コードを data-* 属性に置き換えていることがわかります。
"控えめな検証のコード"
Note
この例では、データ注釈を使った検証の概要が、どのように数行だけの HTML と JavaScript に簡略化されたかを確認しました。 以前の控えめな検証を使わないケースでは、追加する検証コントロールが増えるほど、JavaScript の検証コードも大きくなっていきます。
タスク 2 - データ注釈を使ったモデルの検証
ASP.NET 4.5 では、Web Forms のデータ注釈による検証が導入されています。 各入力に対して検証コントロールを使う代わりに、モデル クラスで制約を定義し、それらを Web アプリケーション全体で使用できるようになりました。 このセクションでは、データ注釈を使って、新しい顧客または顧客の編集フォームを検証する方法について説明します。
CustomerDetail.aspx ページを開きます。 EditItemTemplate セクションと InsertItemTemplate セクションで、顧客の名と姓が RequiredFieldValidator コントロールを使って検証されいることがわかります。 各検証コントロールは特定の条件に関連付けられるため、チェックする条件と同じ数の検証コントロールを含める必要があります。
Customer モデル クラスを検証するために、データ注釈を追加します。 Model フォルダーの Customer.cs クラスを開き、データ注釈属性を使って各プロパティを "装飾" します。
(コード スニペット - "Web Forms ラボ - 演習 2 - データ注釈")
namespace WebFormsLab.Model { using System.Collections.Generic; using System.ComponentModel.DataAnnotations; public class Customer { [Key] public int Id { get; set; } [Required] public string FirstName { get; set; } [Required] public string LastName { get; set; } [Range(0, 130)] public int Age { get; set; } public Address Address { get; set; } [Phone] public string DaytimePhone { get; set; } [EmailAddress, StringLength(256)] public string EmailAddress { get; set; } } }
Note
.NET Framework 4.5 では、既存のデータ注釈コレクションが拡張されています。 [CreditCard]、[Phone]、[EmailAddress]、[Range]、[Compare]、[Url]、[FileExtensions]、[Required]、[Key]、[RegularExpression] などのデータ注釈を使用できます。
いくつかの使用例:
[Key]: 属性が一意識別子であることを指定します
[Range(0.4, 0.5, ErrorMessage="{エラー メッセージを記述}"]: Double の範囲
[EmailAddress(ErrorMessage="Invalid Email"), MaxLength(56)]: 同じ行に 2 つの注釈。
各属性内で独自のエラー メッセージを定義することもできます。
CustomerDetails.aspx を開き、FormView コントロールの EditItemTemplate セクションと InsertItemTemplate セクションにある名フィールドと姓フィールドの RequiredFieldValidator をすべて削除します。
<EditItemTemplate> <fieldset> <p><asp:Label runat="server" AssociatedControlID="firstName">First Name: </asp:Label></p> <p><asp:TextBox runat="server" ID="firstName" Text='<%#: BindItem.FirstName %>' /> <asp:RequiredFieldValidator runat="server" ControlToValidate="firstName" ErrorMessage="Please enter a value for First Name" ForeColor="Red" /> </p> <p><asp:Label runat="server" AssociatedControlID="lastName">Last Name: </asp:Label></p> <p><asp:TextBox runat="server" ID="lastName" Text='<%#: BindItem.LastName %>' /> <asp:RequiredFieldValidator runat="server" ControlToValidate="lastName" ErrorMessage="Please enter a value for Last Name" ForeColor="Red" /> </p> ... <InsertItemTemplate> <fieldset> <p><asp:Label runat="server" AssociatedControlID="firstName">First Name: </asp:Label></p> <p><asp:TextBox runat="server" ID="firstName" Text='<%#: BindItem.FirstName %>' /> <asp:RequiredFieldValidator runat="server" ControlToValidate="firstName" ErrorMessage="Please enter a value for First Name" ForeColor="Red" /> </p> <p><asp:Label runat="server" AssociatedControlID="lastName">Last Name: </asp:Label></p> <p><asp:TextBox runat="server" ID="lastName" Text='<%#: BindItem.LastName %>' /> <asp:RequiredFieldValidator runat="server" ControlToValidate="lastName" ErrorMessage="Please enter a value for Last Name" ForeColor="Red" /> </p> ...
Note
データ注釈を使う利点の 1 つは、各アプリケーション ページで検証ロジックが重複しないことです。 モデルで 1 回定義すれば、データを操作するすべてのアプリケーション ページで使用できます。
CustomerDetails.aspx 分離コードを開き、SaveCustomer メソッドを見つけます。 このメソッドは、新しい顧客を挿入するときに呼び出され、FormView コントロールの値から Customer パラメーターを受け取ります。 ページ コントロールとパラメーター オブジェクト間のマッピングが発生すると、ASP.NET は、すべてのデータ注釈属性に対してモデル検証を実行し、エラーが発生した場合はそれを ModelState 辞書に入力します。
ModelState.IsValid は、検証の実行後にモデルのすべてのフィールドが有効である場合にのみ true を返します。
public void SaveCustomer(Customer customer) { if (this.ModelState.IsValid) { using (var db = new ProductsContext()) { ...
CustomerDetails ページの末尾に ValidationSummary コントロールを追加して、モデルのエラーの一覧を表示します。
</fieldset> </InsertItemTemplate> </asp:FormView> <asp:ValidationSummary runat="server" ShowModelStateErrors="true" ForeColor="Red" HeaderText="Please check the following errors:"/> </asp:Content>
ShowModelStateErrors は ValidationSummary コントロールの新しいプロパティであり、true に設定すると、コントロールに ModelState 辞書のエラーが表示されます。 これらのエラーは、データ注釈の検証から発生したものです。
F5 キーを押して、Web アプリケーションを実行します。 何らかの誤った値をフォームに入力し、[Save] をクリックして検証を実行します。 下部に表示されたエラーの概要に注目してください。
"データ注釈を使った検証"
タスク 3 - ModelState を使ったカスタム データベース エラーの処理
以前のバージョンの Web Forms では、長すぎる文字列や一意キーの違反といったデータベース エラーを処理するために、リポジトリ コードで例外をスローして、分離コードでその例外を処理してエラーを表示する必要がある場合がありました。 比較的単純な処理を行うために、大量のコードが必要になります。
Web Forms 4.5 では、ModelState オブジェクトを使って、モデルのエラーでもデータベースのエラーでも、一貫した方法でページに表示できます。
このタスクでは、ModelState オブジェクトを使って、データベースの例外を適切に処理し、適切なメッセージをユーザーに表示するコードを追加します。
引き続きアプリケーションの実行中に、重複した値を使ってカテゴリの名前を更新してみます。
"重複した名前でカテゴリを更新する"
CategoryName 列の "unique" 制約が原因で、例外がスローされることがわかります。
"重複したカテゴリ名の例外"
デバッグを停止します。 Products.aspx.cs 分離コード ファイルで、UpdateCategory メソッドを更新して、db.SaveChanges() メソッド呼び出しからスローされた例外を処理し、ModelState オブジェクトにエラーを追加するようにします。
新しい TryUpdateModel メソッドでは、ユーザーが提供したフォーム データを使って、データベースから取得したカテゴリ オブジェクトを更新します。
(コード スニペット - "Web Forms ラボ - 演習 2 - UpdateCategory エラー処理")
public void UpdateCategory(int categoryId) { var category = this.db.Categories.Find(categoryId); this.TryUpdateModel(category); if (this.ModelState.IsValid) { try { this.db.SaveChanges(); } catch (DbUpdateException) { var message = string.Format("A category with the name {0} already exists.", category.CategoryName); this.ModelState.AddModelError("CategoryName", message); } } }
Note
理想的には、DbUpdateException の原因を特定し、その根本原因が一意キー制約の違反であるかどうかをチェックする必要があります。
Products.aspx を開き、カテゴリの GridView の下に ValidationSummary コントロールを追加して、モデル エラーの一覧を表示します。
<asp:GridView ID="categoriesGrid" runat="server" ... </asp:GridView> <asp:ValidationSummary ID="ValidationSummary1" runat="server" ShowModelStateErrors="true" /> <h3>Products</h3>
サイトを実行し、Products ページに移動します。 重複した値を使って、カテゴリの名前を更新してみます。
例外が処理され、ValidationSummary コントロールにエラー メッセージが表示されるのがわかります。
"重複したカテゴリのエラー"
タスク 4 - ASP.NET Web Forms 4.5 での要求の検証
ASP.NET の要求の検証機能は、クロスサイト スクリプティング (XSS) 攻撃に対してあるレベルの既定の保護を提供します。 以前のバージョンの ASP.NET では、要求の検証は既定で有効になり、無効にする場合はページ全体で無効にする必要がありました。 新しいバージョンの ASP.NET Web Forms では、1 つのコントロールに対して要求の検証を無効にしたり、要求の遅延検証を実行したり、検証されていない要求のデータにアクセスしたり (その場合は注意が必要) できるようになりました。
Ctrl + F5 キーを押して、デバッグなしでサイトを起動し、Products ページに移動します。 カテゴリを選択し、いずれかの製品の [Edit] リンクをクリックします。
危険な可能性のある内容 (HTML タグなど) を含む説明を入力します。 要求の検証によってスローされる例外に注目してください。
"危険な可能性のある内容で製品を編集する"
"要求の検証によってスローされた例外"
ページを閉じ、Visual Studio で Shift + F5 キーを押してデバッグを停止します。
ProductDetails.aspx ページを開き、Description TextBox を見つけます。
TextBox に新しい ValidateRequestMode プロパティを追加し、その値を Disabled に設定します。
新しい ValidateRequestMode 属性を使うと、各コントロールに対して細かく要求の検証を無効にすることができます。 これは、HTML コードを受け取る可能性のある入力を使いたいが、ページの残りの部分では検証を機能させ続けたい場合に便利です。
<p> <asp:TextBox runat="server" ID="Description" TextMode="MultiLine" Cols="60" Rows="8" Text='<%# BindItem.Description %>' ValidateRequestMode="Disabled" /> </p>
F5 キーを押して、Web アプリケーションを実行します。 製品の編集ページをもう一度開き、HTML タグを含む製品の説明を入力します。 これで、HTML コンテンツを説明に追加できるようになったことがわかります。
"製品の説明に対して要求の検証が無効になっている"
Note
運用アプリケーションでは、ユーザーが入力した HTML コードをサニタイズして、安全な HTML タグだけが入力されるようにする必要があります (たとえば、<script> タグがないこと)。 これを行うには、Microsoft の Web 保護ライブラリを使用できます。
製品をもう一度編集します。 [Name] フィールドに HTML コードを入力し、[Save] をクリックします。 要求の検証は [Description] フィールドでのみ無効になっており、残りのフィールドは引き続き危険な可能性のある内容に対して検証されることがわかります。
"残りのフィールドでは要求の検証が有効になっている"
ASP.NET Web Forms 4.5 には、要求の検証を遅延実行するための新しい要求の検証モードが含まれています。 要求の検証モードを 4.5 に設定すると、コードの一部が Request.Form["key"] にアクセスした場合、ASP.NET 4.5 の要求の検証は、フォーム コレクション内のその特定の要素に対する要求の検証のみをトリガーします。
さらに、ASP.NET 4.5 には、Microsoft Anti-XSS Library v4.0 のコア エンコード ルーチンが含まれるようになりました。 Anti-XSS エンコード ルーチンは、新しい System.Web.Security.AntiXss 名前空間にある新しい AntiXssEncoder 型によって実装されています。 AntiXssEncoder を使用するように encoderType パラメーターを構成すると、ASP.NET 内のすべての出力エンコードで自動的に新しいエンコード ルーチンが使用されます。
ASP.NET 4.5 の要求の検証では、要求データへの検証されていないアクセスもサポートされます。 ASP.NET 4.5 では、Unvalidated という名前の新しいコレクション プロパティが HttpRequest オブジェクトに追加されています。 HttpRequest.Unvalidated を参照すると、Form、QueryString、Cookies、URL など、要求データのすべての一般的な要素にアクセスできます。
"Request.Unvalidated オブジェクト"
Note
HttpRequest.Unvalidated プロパティは慎重に使用してください。 生の要求データに対しては慎重にカスタム検証を実行し、危険なテキストがラウンドトリップされて知らない顧客にレンダリングされることがないようにしてください。
演習 3: ASP.NET Web Forms での非同期ページ処理
この演習では、ASP.NET Web Forms の新しい非同期ページ処理機能について説明します。
タスク 1 - 画像をアップロードして表示するために製品の詳細ページを更新する
このタスクでは、製品の詳細ページを更新して、ユーザーが製品の画像 URL を指定し、読み取り専用ビューで表示できるようにします。 指定された画像を同期的にダウンロードして、そのローカル コピーを作成します。 次のタスクでは、この実装を更新して非同期的に動作するようにします。
Visual Studio 2012 を開き、このラボのフォルダーの Source\Ex3-Async\Begin にある Begin ソリューションを読み込みます。 または、前の演習から既存のソリューションで引き続き作業することもできます。
提供された Begin ソリューションを開いた場合は、続行する前に、不足している NuGet パッケージをダウンロードする必要があります。 これを行うには、ソリューション エクスプローラーで WebFormsLab プロジェクトをクリックし、[NuGet パッケージの管理] を選択します。
[NuGet パッケージの管理] ダイアログで、[復元] をクリックして、不足しているパッケージをダウンロードします。
最後に、[ビルド] | [ソリューションのビルド] をクリックしてソリューションをビルドします。
Note
NuGet を使用する利点の 1 つは、プロジェクト内のすべてのライブラリを発送する必要がなく、プロジェクト サイズが縮小される点です。 NuGet Power Tools では、Packages.config ファイルでパッケージのバージョンを指定することで、プロジェクトを初回実行するときに必要なすべてのライブラリをダウンロードできます。 このラボから既存のソリューションを開いた後に、これらの手順を実行する必要があるのは、このような理由によります。
ProductDetails.aspx ページのソースを開き、FormView の ItemTemplate に製品画像を表示するフィールドを追加します。
<ItemTemplate> <fieldset> <p><b><asp:Label ID="Label2" runat="server" AssociatedControlID="itemProductName">Name:</asp:Label></b></p> <p><asp:Label runat="server" ID="itemProductName" Text='<%#: Item.ProductName %>' /></p> <p><b><asp:Label ID="Label3" runat="server" AssociatedControlID="itemDescription">Description (HTML):</asp:Label></b></p> <p><asp:Label runat="server" ID="itemDescription" Text='<%# Item.Description %>' /></p> <p><b><asp:Label ID="Label4" runat="server" AssociatedControlID="itemUnitPrice">Price:</asp:Label></b></p> <p><asp:Label runat="server" ID="itemUnitPrice" Text='<%#: Item.UnitPrice %>' /></p> <p><b><asp:Label ID="Label5" runat="server" AssociatedControlID="itemUnitPrice">Image:</asp:Label></b></p> <p> <img src="<%# string.IsNullOrEmpty(Item.ImagePath) ? "/Images/noimage.jpg" : Item.ImagePath %>" alt="Image" /> </p> <br /> <p> <asp:Button ID="Button1" runat="server" CommandName="Edit" Text="Edit" /> <asp:HyperLink NavigateUrl="~/Products.aspx" Text="Back" runat="server" /> </p> </fieldset> </ItemTemplate>
FormView の EditTemplate に画像 URL を指定するフィールドを追加します。
<fieldset> <p><asp:Label ID="Label2" runat="server" AssociatedControlID="ProductName">Name:</asp:Label></p> <p><asp:TextBox runat="server" ID="ProductName" Text='<%#: BindItem.ProductName %>' /></p> <p><asp:Label ID="Label3" runat="server" AssociatedControlID="Description">Description (HTML):</asp:Label></p> <p> <asp:TextBox runat="server" ID="Description" TextMode="MultiLine" Cols="60" Rows="8" Text='<%# BindItem.Description %>' ValidateRequestMode="Disabled" /> </p> <p><asp:Label ID="Label4" runat="server" AssociatedControlID="UnitPrice">Price:</asp:Label></p> <p><asp:TextBox runat="server" ID="UnitPrice" Text='<%#: BindItem.UnitPrice %>' /></p> <p><asp:Label ID="Label1" runat="server" AssociatedControlID="ImagePath">Image URL:</asp:Label></p> <p><asp:TextBox runat="server" ID="ImagePath" Text='<%#: BindItem.ImagePath %>' /></p> <br /> <p> <asp:Button runat="server" CommandName="Update" Text="Save" /> <asp:Button runat="server" CommandName="Cancel" Text="Cancel" CausesValidation="false" /> </p> </fieldset>
ProductDetails.aspx.cs 分離コード ファイルを開き、次の名前空間ディレクティブを追加します。
(コード スニペット - "Web Forms ラボ - 演習 3 - 名前空間")
using System.IO; using System.Net; using System.Web;
リモート画像をローカルの Images フォルダーに格納し、新しい画像の場所の値で製品エンティティを更新する UpdateProductImage メソッドを作成します。
(コード スニペット - "Web Forms ラボ - 演習 3 - UpdateProductImage")
private void UpdateProductImage(Product product) { string imageUrl = product.ImagePath; if (!string.IsNullOrEmpty(imageUrl) && !VirtualPathUtility.IsAbsolute(imageUrl)) { product.ImagePath = string.Format( "/Images/{0}{1}", product.ProductId, Path.GetExtension(imageUrl)); using (var wc = new WebClient()) { wc.DownloadFile(imageUrl, Server.MapPath(product.ImagePath)); } } }
UpdateProductImage メソッドを呼び出すように UpdateProduct メソッドを更新します。
(コード スニペット - "Web Forms ラボ - 演習 3 - UpdateProductImage の呼び出し")
public void UpdateProduct(int productId) { var product = this.db.Products.Find(productId); this.TryUpdateModel(product); this.UpdateProductImage(product); if (this.ModelState.IsValid) { this.db.SaveChanges(); } }
アプリケーションを実行し、製品の画像をアップロードしてみます。
"製品の画像の設定"
タスク 2 - 製品の詳細ページに非同期処理を追加する
このタスクでは、非同期的に動作するように製品の詳細ページを更新します。 ASP.NET 4.5 の非同期ページ処理を使って、実行時間の長いタスク (画像のダウンロード プロセス) を強化します。
Web アプリケーションで非同期メソッドを使うと、ASP.NET スレッド プールが使用される方法を最適化できます。 ASP.NET のスレッド プールでは要求に参加するためのスレッドの数が限られているため、すべてのスレッドがビジー状態になると、ASP.NET は新しい要求を拒否し始め、アプリケーション エラー メッセージを送信し、サイトは利用不可になります。
Web サイト上の時間のかかる操作は、割り当てられたスレッドを長時間占有するため、非同期プログラミングの対象として適しています。 これには、実行時間の長い要求、大量の異なる要素を含むページ、データベースのクエリや外部 Web サーバーへのアクセスなどのオフライン操作を必要とするページなどが含まれます。 こうした操作に非同期メソッドを使う利点は、そのページの処理中にスレッドが解放されてスレッド プールに返されるため、それを新しいページ要求への参加に使用できるということです。 つまり、そのページはスレッド プールの 1 つのスレッドで処理を開始し、非同期処理の完了後に、別のスレッドで処理を完了する可能性があるということです。
ProductDetails.aspx ページを開きます。 Page 要素に Async 属性を追加して、true に設定します。 この属性を使って、IHttpAsyncHandler インターフェイスを実装するように ASP.NET に指示します。
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="ProductDetails.aspx.cs" Inherits="WebFormsLab.ProductDetails" Async="true" %>
ページの下部に Label を追加して、ページを実行しているスレッドの詳細を表示します。
<EmptyDataTemplate>Product not found</EmptyDataTemplate> </asp:FormView> <asp:Label ID="threadsMessageLabel" runat="server" /> </asp:Content>
ProductDetails.aspx.cs を開き、次の名前空間ディレクティブを追加します。
(コード スニペット - "Web Forms ラボ - 演習 3 - 名前空間 2")
using System.Web.UI; using System.Threading;
非同期タスクで画像をダウンロードするように UpdateProductImage メソッドを変更します。 WebClient DownloadFile メソッドを DownloadFileTaskAsync メソッドに置き換え、await キーワードを含めます。
(コード スニペット - "Web Forms ラボ - 演習 3 - UpdateProductImage 非同期")
private void UpdateProductImage(Product product) { string imageUrl = product.ImagePath; if (!string.IsNullOrEmpty(imageUrl) && !VirtualPathUtility.IsAbsolute(imageUrl)) { product.ImagePath = string.Format( "/Images/{0}{1}", product.ProductId, Path.GetExtension(imageUrl)); this.RegisterAsyncTask(new PageAsyncTask(async (t) => { using (var wc = new WebClient()) { await wc.DownloadFileTaskAsync(imageUrl, this.Server.MapPath(product.ImagePath)); } })); } }
RegisterAsyncTask は、別のスレッドで実行される新しいページの非同期タスクを登録します。 これは、実行される Task (t) を含むラムダ式を受け取ります。 DownloadFileTaskAsync メソッドの await キーワードにより、このメソッドの残りの部分は、DownloadFileTaskAsync メソッドの完了後に非同期的に呼び出されるコールバックに変換されます。 ASP.NET は、自動的に HTTP 要求の元の値をすべて保持して、このメソッドの実行を再開します。 .NET 4.5 の新しい非同期プログラミング モデルを使うと、同期コードと非常によく似た非同期コードを記述することができ、コールバック関数や継続コードの複雑な処理をコンパイラに行わせることができます。
Note
RegisterAsyncTask と PageAsyncTask は、.NET 2.0 から既に使用できました。 await キーワードは .NET 4.5 の非同期プログラミング モデルからの新機能であり、.NET WebClient オブジェクトの新しい TaskAsync メソッドと共に使用できます。
コードの実行が開始したスレッドと終了したスレッドを表示するコードを追加します。 これを行うには、次のコードを使って UpdateProductImage メソッドを更新します。
(コード スニペット - "Web Forms ラボ - 演習 3 - スレッドの表示")
private void UpdateProductImage(Product product) { string imageUrl = product.ImagePath; if (!string.IsNullOrEmpty(imageUrl) && !VirtualPathUtility.IsAbsolute(imageUrl)) { product.ImagePath = string.Format( "/Images/{0}{1}", product.ProductId, Path.GetExtension(imageUrl)); this.RegisterAsyncTask(new PageAsyncTask(async (t) => { var startThread = Thread.CurrentThread.ManagedThreadId; using (var wc = new WebClient()) { await wc.DownloadFileTaskAsync(imageUrl, this.Server.MapPath(product.ImagePath)); } var endThread = Thread.CurrentThread.ManagedThreadId; this.threadsMessageLabel.Text = string.Format("Started on thread: {0}<br /> Finished on thread: {1}", startThread, endThread); })); } }
Web サイト の Web.config ファイルを開きます。 次の appSetting 変数を追加します。
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>
F5 キーを押してアプリケーションを実行し、製品の画像をアップロードします。 コードが開始したスレッドと終了したスレッドの ID が異なる場合があることに注目してください。 これは、非同期タスクが ASP.NET スレッド プールの別のスレッドで実行されたためです。 タスクが完了すると、ASP.NET がそのタスクをキューに戻し、使用可能なスレッドのいずれかを割り当てます。
"画像を非同期的にダウンロードする"
Note
さらに、「付録 B: Web 配置を使用した ASP.NET MVC 4 アプリケーションの発行」に従 って、このアプリケーションを Azure にデプロイできます。
まとめ
このハンズオン ラボでは以下の概念に取り組み、そのデモを行いました。
- 厳密に型指定されたデータ バインディング式を使う
- Web Forms の新しいモデル バインド機能を使う
- ページ データを分離コード メソッドにマップするために値プロバイダーを使う
- ユーザーによる入力の検証にデータ注釈を使う
- Web Forms の jQuery を使った控えめなクライアント側検証を利用する
- きめ細かい要求の検証を実装する
- Web Forms で非同期ページ処理を実装する
付録 A: Visual Studio Express 2012 for Web のインストール
Microsoft Web Platform Installer を使用して、Microsoft Visual Studio Express 2012 for Web または別の "Express" バージョンをインストールできます。 次の手順では、Microsoft Web Platform Installer を使用して Visual studio Express 2012 for Web をインストールするために必要な手順について説明します。
[https://go.microsoft.com/?linkid=9810169] (https://go.microsoft.com/?linkid=9810169) に移動します。 または、Web Platform Installer を既にインストールしている場合は、それを開いて製品 "Visual Studio Express 2012 for Web with Azure SDK" を検索することもできます。
[今すぐインストール] をクリックします。 Web Platform Installer がない場合は、最初にダウンロードしてインストールするようにリダイレクトされます。
Web Platform Installer が開いたら、[インストール] をクリックしてセットアップを開始します。
Visual Studio Express をインストールする
すべての製品のライセンスと使用条件を読み、[同意する] をクリックして続行します。
ライセンス条項に同意する
ダウンロードとインストールのプロセスが完了するまで待機します。
インストールの進行状況
インストールが完了したら、[完了] をクリックします。
インストールの完了
[終了] をクリックして Web Platform Installer を閉じます。
Visual Studio Express for Web を開くには、[スタート] 画面に移動し、「VS Express」と記入して、[VS Express for Web] タイルをクリックします。
VS Express for Web タイル
付録 B: Web 配置を使用した ASP.NET MVC 4 アプリケーションの発行
この付録では、Azure Portal から新しい Web サイトを作成し、ラボに従って取得したアプリケーションを発行する方法について説明します。ここでは、Azure によって提供される Web 配置発行機能を利用します。
タスク 1 - 新しい Web サイトの Azure Portal からの作成
Azure 管理ポータルに移動し、サブスクリプションに関連付けられている Microsoft 資格情報を使用してサインインします。
Note
Azure では、10 の ASP.NET Web サイトを無料でホストし、トラフィックの増加に合わせてスケーリングできます。 こちらからサインアップできます。
ポータルへのログオン
コマンド バーの [新規] をクリックします。
新しい Web サイトの作成
[コンピューティング] | [Web サイト] をクリックします。 続いて、[簡易作成] オプションを選択します。 新しい Web サイト用の使用可能な URL を指定し、[Web サイトの作成] をクリックします。
Note
Azure は、制御および管理が可能なクラウドで実行される Web アプリケーションのホストです。 [簡易作成] オプションを使用すると、完成した Web アプリケーションをポータルの外部から Azure にデプロイできます。 データベースをセットアップするための手順は含まれません。
簡易作成を使用した新しい Web サイトの作成
新しい Web サイトが作成されるまで待機します。
Web サイトが作成されたら、URL 列の下にあるリンクをクリックします。 新しい Web サイトが動作していることを確認します。
新しい Web サイトの参照
実行中の Web サイト
ポータルに戻って [名前] 列の下にある Web サイトの名前をクリックして、管理ページを表示します。
Web サイト管理ページを開く
[ダッシュボード] ページの [概要] セクションで、[発行プロファイルのダウンロード] リンクをクリックします。
Note
"発行プロファイル" には、有効なそれぞれの発行方法について、Web アプリケーションを Azure に発行するために必要なすべての情報が含まれています。 発行プロファイルには、発行メソッドが有効化される各エンドポイントに接続して認証するために必要な URL、ユーザーの資格情報、およびデータベース文字列が含まれています。 Microsoft WebMatrix 2、Microsoft Visual Studio Express for Web および Microsoft Visual Studio 2012 では、Web アプリケーションを Azure に発行するためのこれらのプログラムの構成を自動化するための発行プロファイルの読み取りがサポートされています。
Web サイト発行プロファイルのダウンロード
発行プロファイル ファイルを既知の場所にダウンロードします。 この演習ではさらに、このファイルを使用して Visual Studio から Azure に Web アプリケーションを発行する方法について説明します。
発行プロファイル ファイルの保存
タスク 2 - データベース サーバーの構成
アプリケーションで SQL Server データベースを使用する場合は、SQL Database サーバーを作成する必要があります。 SQL Server を使用しないシンプルなアプリケーションをデプロイする場合は、このタスクをスキップできます。
アプリケーション データベースを格納するための SQL Database サーバーが必要です。 Azure 管理ポータルのサブスクリプションから SQL Database サーバーを表示するには、[SQL データベース] | [サーバー] | [サーバーのダッシュボード] を使用します。 サーバーを作成していない場合は、コマンド バーの [追加] ボタンを使用して作成できます。 次のタスクで使用するため、サーバー名と URL、管理者ログイン名、パスワードを書き留めます。 データベースは後の段階で作成されるため、まだ作成しないでください。
SQL Database サーバー ダッシュボード
次のタスクでは、Visual Studio からのデータベース接続をテストします。そのため、ローカル IP アドレスをサーバーの許可された IP アドレスの一覧に含める必要があります。 これを行うには、[構成] をクリックし、[現在のクライアント IP アドレス] から IP アドレスを選択し、[開始 IP アドレス] テキスト ボックスと [終了 IP アドレス] テキスト ボックスに貼り付けて、
ボタンをクリックします。
クライアント IP アドレスの追加
クライアント IP アドレスが許可された IP アドレスの一覧に追加されたら、[保存] をクリックして変更を確認します。
変更を確認
タスク 3 - Web 配置を使用した ASP.NET MVC 4 アプリケーションの発行
ASP.NET MVC 4 ソリューションに戻ります。 ソリューション エクスプローラーで、プロジェクトを右クリックして [発行] を選択します。
Web サイトの発行
最初のタスクで保存した発行プロファイルをインポートします。
公開プロファイルのインポート
[接続の検証] をクリックします。 検証が完了したら、[次へ] をクリックします。
Note
[接続の検証] ボタンの横に緑色のチェックマークが表示されたら、検証は完了しています。
接続の検証
[設定] ページの [データベース] セクションで、データベース接続のテキスト ボックス (DefaultConnection) の横にあるボタンをクリックします。
Web 配置の構成
次のようにデータベース接続を構成します:
[サーバー名] に、tcp: プレフィックスを使用して SQL Database サーバーの URL を入力します。
[ユーザー名] に、サーバー管理者のログイン名を入力します。
[パスワード] に、サーバー管理者のログイン パスワードを入力します。
新しいデータベース名を入力します。
同期先接続文字列の構成
次に、 [OK] をクリックします データベースの作成を求められたら、[はい] をクリックします。
データベースの作成
Azure の SQL Database への接続に使用する接続文字列は、[既定の接続] ボックス内に表示されます。 続けて、 [次へ] をクリックします。
SQL Database を指す接続文字列
[プレビュー] ページで [発行] をクリックします。
Web アプリケーションの発行
発行プロセスが完了すると、既定のブラウザーで発行された Web サイトが開きます。
付録 C: コード スニペットの使用
コード スニペットを使用すると、必要なすべてのコードをすぐに入手できます。 ラボ ドキュメントでは、次の図に示すように、使用できるタイミングが正確に示されています。
Visual Studio コード スニペットを使用してプロジェクトにコードを挿入する
キーボードを使用してコード スニペットを追加するには (C# のみ)
- コードを挿入する場所にカーソルを置きます。
- スニペット名の入力を開始します (スペースやハイフンは使用しない)。
- IntelliSense に対応するスニペットの名前が表示されるのを確認します。
- 適切なスニペットを選択します (または、スニペットの名前が完全に選択されるまで入力を続けます)。
- Tab キーを 2 回押して、カーソル位置にスニペットを挿入します。
スニペット名の入力を開始する
Tab キーを押して強調表示されたスニペットを選択する
Tab キーをもう一度押すと、スニペットが展開される
マウスを使用してコード スニペット追加するには (C#、Visual Basic、XML) 1. コード スニペットを挿入する場所を右クリックします。
- [スニペットの挿入] に続いて [マイ コード スニペット] を選択します。
- 関連するスニペットをクリックして、一覧から選択します。
コード スニペットを挿入する場所を右クリックし、[スニペットの挿入] を選択する
関連するスニペットをクリックして、一覧から選択する