ASP.NET では行わないことと、その代わりに行うこと

このトピックでは、ASP.NET Web プロジェクトでよくある開発者の誤りについて説明します。 ここでは、これらの一般的な間違いを回避するために実行する必要がある推奨事項を提供します。 これは、ノルウェー開発者会議での Damian Edwardsプレゼンテーションに基づいています。

免責情報

このトピックは、アプリケーションが安全で効率的であることを確認するための包括的ガイドではありません。 セキュリティとパフォーマンスのベスト プラクティスはこのトピックでは説明されおらず、それらに従う必要があります。 これは、.NET のクラスとプロセスに関連する一般的な間違いを回避する方法のみを示しています。

概要

このトピックは、次のセクションで構成されています。

標準へのコンプライアンス

コントロール アダプター

推奨事項: アダプティブ レンダリングのためにコントロール アダプターを使用するのをやめ、代わりに、CSS メディア クエリと標準に準拠した HTML を使用します。

コントロール アダプターは、さまざまなデバイスや環境に合わせてカスタマイズされたプレゼンテーション コードをレンダリングする目的で、.NET 2.0 で導入されました。 現在は、このアダプティブ レンダリングは、CSS と HTML で実現できます。 コントロール アダプターの使用を停止し、既存のアダプターを CSS と HTML に転換する必要があります。

詳細については、「メディア クエリ」と「方法: ASP.NET Web Forms または MVC アプリケーションにモバイル ページを追加する」を参照してください。

コントロールのスタイル プロパティ

推奨事項: コントロール マークアップでのスタイル値の設定を停止し、代わりに、CSS スタイルシートで書式設定値を設定します。

Web サーバー コントロールには、インライン スタイルのプロパティを設定するために使用できる多数のプロパティが含まれています。 たとえば、ForeColor プロパティは、コントロールのテキストの色を設定します。 CSS スタイルシートを使用すると、この同じ効果を、より効率的に実現できます。 スタイルシートを使用すると、スタイル値を一元化でき、アプリケーション全体にわたってこれらの値を設定することを回避できます。

次の例は、テキストを赤に設定する CSS クラスを示しています。

.CautionRow {
    color: red;
}

次の例は、CSS クラスを動的に適用する方法を示しています。

protected void CustomersGridView_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.Cells[2].Text == "Unconfirmed")
    {
        e.Row.CssClass = "CautionRow";
    }
}

ページ コールバックとコントロール コールバック

推奨事項: ページとコントロールのコールバックの使用を停止し、代わりに、AJAX、UpdatePanel、MVC の各アクション メソッド、Web API、SignalR のいずれかを使用します。

以前のバージョンの ASP.NET では、ページ コールバック メソッドとコントロール コールバック メソッドを使用すると、Web ページ全体を更新せずに、一部を更新できました。 現在は、部分的なページ更新は、AJAXUpdatePanelMVCWeb APISignalR を使用して実行できるようになりました。 コールバック メソッドの使用は、フレンドリ URL とルーティングに関する問題を引き起こす可能性があるため、使用を停止する必要があります。 既定では、コントロールはコールバック メソッドを有効にしませんが、コントロールでこの機能を有効にした場合は無効にする必要があります。

ブラウザー機能の検出

推奨事項: 静的ブラウザー機能の検出の使用を停止し、代わりに動的機能検出を使用します。

以前のバージョンの ASP.NET では、各ブラウザーでサポートされている機能は、XML ファイルに格納されていました。 静的ルックアップを使用して機能のサポートを検出することは、最適な方法ではありません。 現在は、Modernizr などの機能検出フレームワークを使用して、ブラウザーでサポートされている機能を動的に検出できるようになりました。 機能検出では、メソッドまたはプロパティを使用し、ブラウザーが目的の結果を生成したかどうかを確認チェックすることで、サポートが決定されます。 既定では、Modernizr は Web アプリケーション テンプレートに含まれています。

セキュリティ

要求の検証

推奨事項: ユーザー入力を検証し、ユーザーからの出力をエンコードします。

要求の検証は、各要求を検査し、認識された脅威が見つかった場合に、要求を停止する ASP.NET の機能です。 クロスサイト スクリプティング攻撃からアプリケーションをセキュリティで保護する目的で、要求の検証に依存するのはやめてください。 代わりに、ユーザーからの入力をすべて検証し、出力をエンコードします。 場合によっては、正規表現を使用して入力を検証できますが、より複雑な事例では、値が許容値と一致するかどうかを判断する .NET クラスを使用してユーザー入力を検証する必要があります。

次の例は、URI クラスで静的メソッドを使用して、ユーザーによって提供される URI が有効かどうかを判断する方法を示しています。

var isValidUri = Uri.IsWellFormedUriString(passedUri, UriKind.Absolute);

ただし、URI を十分に検証するには、URI が http または https を指定していることを確認する必要もあります。 次の例では、インスタンス メソッドを使用して URI が有効であることを確認します。

var uriToVerify = new Uri(passedUri);
var isValidUri = uriToVerify.IsWellFormedOriginalString();
var isValidScheme = uriToVerify.Scheme == "http" || uriToVerify.Scheme == "https";

ユーザー入力を HTML としてレンダリングするか、SQL クエリにユーザー入力を含める前に、悪意のあるコードが含まれていないように値をエンコードします。

次に示すように、<%: %> 構文を使用して、マークアップ内の値を HTML エンコードできます。

<span><%: userInput %></span>

または、Razor 構文では、次に示すように、@ を使用して HTML エンコードできます。

<span>@userInput</span>

次の例では、分離コードで値を HTML エンコードする方法を示します。

var encodedInput = Server.HtmlEncode(userInput);

SQL コマンドの値を安全にエンコードするには、SqlParameter などのコマンド パラメーターを使用します。

Cookie なしのフォーム認証とセッション

推奨事項: Cookie を要求します。

クエリ文字列内で認証情報を渡すことは安全ではありません。 そのため、アプリケーションに認証が含まれる場合は、Cookie を要求します。 Cookie に機密情報が格納されている場合は、Cookie のために SSL を要求することを検討してください。

次の例は、Web.config ファイル内に、フォーム認証が SSL 経由で送信される Cookie を要求することを指定する方法を示しています。

<authentication mode="Forms">
  <forms loginUrl="member_login.aspx"
    cookieless="UseCookies"
    requireSSL="true"
    path="/MyApplication" />
</authentication>

EnableViewStateMac

推奨事項: false に設定しません。

既定では、EnableViewStateMac は true に設定されています。 アプリケーションがビュー状態を使用していない場合でも、EnableViewStateMac を false に設定しないでください。 この値を false に設定すると、アプリケーションはクロスサイト スクリプティングに対して脆弱になります。

ASP.NET 4.5.2 以降、ランタイムは EnableViewStateMac=true を強制的に適用します。 false に設定した場合でも、ランタイムはこの値を無視し、値を true に設定して続行します。 詳細については、ASP.NET 4.5.2 と EnableViewStateMac に関する記事を参照してください。

次の例は、EnableViewStateMac を true に設定する方法を示しています。 既定では true であるため、この値を実際に true に設定する必要はありません。 ただし、アプリケーション内の任意のページで false に設定した場合は、すぐにこの値を修正する必要があります。

<%@ Page language="C#" EnableViewStateMac="true" %>

中程度の信頼

推奨事項: セキュリティ境界として中程度の信頼 (またはその他の信頼レベル) に依存しないでください。

部分的信頼はアプリケーションを適切に保護しないため、使用しないでください。 代わりに、完全な信頼を使用し、信頼されていないアプリケーションは別のアプリケーション プールに分離します。 また、各アプリケーション プールを一意の ID で実行します。 詳細については、ASP.NET 部分信頼でアプリケーションの分離が保証されないことに関する記事を参照してください。

<appSettings>

推奨事項: <appSettings> 要素のセキュリティ設定を無効にしないでください。

appSettings 要素には、セキュリティ更新プログラムに必要な多くの値が含まれています。 これらの値は変更または無効にしないでください。 更新プログラムのデプロイ時にこれらの値を無効にする必要がある場合は、デプロイの完了後すぐに再度有効にします。

詳細については、「ASP.NET appSettings 要素」を参照してください。

UrlPathEncode

推奨事項: 代わりに、UrlEncode を使用してください。

UrlPathEncode メソッドは、ある特定のブラウザー互換性の問題を解決する目的で .NET Framework に追加されました。 このメソッドでは URL は適切にエンコードされず、クロスサイト スクリプティングからアプリケーションは保護されません。 作成するアプリケーションで使用しないでください。 代わりに、UrlEncode を使用します。

次の例は、ハイパーリンク コントロールのクエリ文字列パラメーターとしてエンコードされた URL を渡す方法を示しています。

string destinationURL = "http://www.contoso.com/default.aspx?user=test";
NextPage.NavigateUrl = "~/Finish?url=" + Server.UrlEncode(destinationURL);

信頼性とパフォーマンス

PreSendRequestHeaders と PreSendRequestContent

推奨事項: これらのイベントは、マネージド モジュールでは使用しないでください。 代わりに、必要なタスクを実行するネイティブ IIS モジュールを記述します。 「ネイティブ コード HTTP モジュールの作成」を参照してください。

ネイティブ IIS モジュールで PreSendRequestHeaders イベントと PreSendRequestContent イベントを使用できます。

警告

PreSendRequestHeadersPreSendRequestContent は、IHttpModule を実装するマネージド モジュールと一緒には使用しないでください。 これらのプロパティを設定すると、非同期要求で問題が発生する可能性があります。 アプリケーション要求ルーティング (ARR) と WebSocket の組み合わせにより、アクセス違反の例外が発生し、w3wp がクラッシュする可能性があります。 この例に、iiscore.dll の iiscore!W3_CONTEXT_BASE::GetIsLastNotification+68 が引き起こしたアクセス違反の例外 (0xC0000005) がありました。

Web Forms を使用した非同期ページ イベント

推奨事項: Web Forms では、Page ライフサイクル イベントに非同期 void メソッドを記述しないようにし、代わりに、非同期コードに Page.RegisterAsyncTask を使用します。

ページ イベントを asyncvoid を使用してマークすると、非同期コードがいつ終了したかを判断できません。 代わりに、Page.RegisterAsyncTask を使用して、完了を追跡できる方法で非同期コードを実行します。

次の例は、非同期コードを含むボタン クリック ハンドラーを示しています。 この例には、文字列値の非同期読み取りが含まれます。これは、推奨される方法ではなく、非同期タスクの簡略化された例としてのみ提供されています。

protected void StartAsync_Click(object sender, EventArgs e)
{
    Page.RegisterAsyncTask(new PageAsyncTask(async() =>
    {
        string stringToRead = "Long text value";

        using (StringReader reader = new StringReader(stringToRead))
        {
            string readText = await reader.ReadToEndAsync();
            Result.Text = readText;
        }
    }));
}

非同期タスクを使用している場合は、Web.config ファイルで Http ランタイム ターゲット フレームワークを 4.5 (またはそれ以降) に設定します。 ターゲット フレームワークを 4.5 に設定すると、.NET 4.5 で追加された新しい同期コンテキストが有効になります。 この値は、Visual Studio の新しいプロジェクトでは既定で設定されますが、既存のプロジェクトを使用している場合は設定されません。

<system.web>
    <httpRuntime TargetFramework="4.5" />
</system.web>

ファイア アンド フォーゲット処理

推奨事項: ASP.NET 内で要求を処理する場合は、ファイア アンド フォーゲット処理 (ThreadPool.QueueUserWorkItem メソッドの呼び出しやデリゲートを繰り返し呼び出すタイマーの作成など) を実行しないでください。

アプリケーションに、ASP.NET 内で実行されるファイア アンド フォーゲット処理がある場合、アプリケーションは同期を失う可能性があります。アプリ ドメインはいつでも破棄される可能性があります。つまり、進行中のプロセスがアプリケーションの現在の状態と一致しなくなる可能性があります。

この種類の作業は、ASP.NET の外部に移動する必要があります。 Azure の Web ジョブ、Windows サービス、または worker ロールを使用して、継続的な作業を実行し、別のプロセスからそのコードを実行できます。

ASP.NET 内でこの作業を実行する必要がある場合は、WebBackgrounder という名前の Nuget パッケージを追加してコードを実行できます。

要求エンティティの本文

推奨事項: ハンドラーの実行イベントの前に、Request.Form または Request.InputStream を読み取らないでください。

Request.Form または Request.InputStream から読み取る最も早いタイミングは、ハンドラーの実行イベント中にする必要があります。 MVC では、コントローラーはハンドラーであり、実行イベントはアクション メソッドの実行時です。 Web Forms では、Page がハンドラーであり、実行イベントは、Page.Init イベントが発生したときです。 実行イベントより前に要求エンティティの本文を読み取った場合、要求の処理に干渉します。

実行イベントの前に要求エンティティの本文を読み取る必要がある場合は、Request.GetBufferlessInputStreamRequest.GetBufferedInputStream のいずれかを使用します。 GetBufferlessInputStream を使用すると、要求から生ストリームを取得し、要求全体を処理する責任を負います。 GetBufferlessInputStream を呼び出した後、Request.Form と Request.InputStream は、ASP.NET によって設定されていないため使用できません。 GetBufferedInputStream を使用すると、要求からストリームのコピーが取得されます。 Request.Form と Request.InputStream は、ASP.NET が他のコピーを設定するため、要求の後半で引き続き使用できます。

Response.Redirect と Response.End

推奨事項: Response.Redirect(String) の呼び出し後のスレッドの処理方法の違いに注意してください。

Response.Redirect(String) メソッドは Response.End メソッドを呼び出します。 同期プロセスでは、Request.Redirect を呼び出すと、現在のスレッドが直ちに中止されます。 ただし、非同期プロセスでは、Response.Redirect を呼び出しても現在のスレッドは中止されないため、要求に対してコードの実行が続行されます。 非同期プロセスでは、メソッドから Task を返してコードの実行を停止する必要があります。

MVC プロジェクトでは、Response.Redirect を呼び出さないでください。 代わりに、RedirectResult を返します。

EnableViewState と ViewStateMode

推奨事項: ビュー状態を使用するコントロールをきめ細かく制御するには、EnableViewState の代わりに ViewStateMode を使用します。

Page ディレクティブで EnableViewState を false に設定すると、ページ内のすべてのコントロールに対してビュー状態が無効になり、有効にすることはできません。 ページ内の特定のコントロールに対してのみビュー状態を有効にする場合は、Page の ViewStateMode を無効に設定します。

<%@ Page ViewStateMode="Disabled" . . . %>

次に、実際にビュー状態が必要なコントロールでのみ、ViewStateMode を Enabled に設定します。

<asp:GridView ViewStateMode="Enabled" runat="server">

必要なコントロールに対してのみビュー状態を有効にすると、Web ページのビュー状態のサイズを縮小できます。

SqlMembershipProvider

推奨事項: ユニバーサル プロバイダーを使用します。

現在のプロジェクト テンプレートでは、SqlMembershipProvider は、NuGet パッケージとして使用できる ASP.NET ユニバーサル プロバイダーに置き換えられました。 以前のバージョンのテンプレートでビルドされたプロジェクトで SqlMembershipProvider を使用している場合は、ユニバーサル プロバイダーに切り替える必要があります。 ユニバーサル プロバイダーは、Entity Framework でサポートされているすべてのデータベースで動作します。

詳細については、「ASP.NET ユニバーサル プロバイダーの概要」を参照してください。

実行時間の長い要求 (>110 秒)

推奨事項: 接続されているクライアントに対して WebSocket または SignalR を使用し、非同期 I/O 操作を使用します。

実行時間の長い要求により、予期しない結果が発生し、Web アプリケーションのパフォーマンスが低下する可能性があります。 要求の既定のタイムアウト設定は 110 秒です。 実行時間の長い要求でセッション状態を使用している場合、ASP.NET は 110 秒後に Session オブジェクトのロックを解放します。 ただし、ロックが解除されると、アプリケーションが Session オブジェクトに対する操作の途中にあり、操作が正常に完了しない可能性があります。 最初の要求の実行中にユーザーからの 2 番目の要求がブロックされた場合、2 番目の要求は不整合な状態で Session オブジェクトにアクセスする可能性があります。

アプリケーションにブロック (または同期) I/O 操作が含まれている場合、アプリケーションは応答しなくなります。

パフォーマンスを向上させるには、.NET Framework で非同期 I/O 操作を使用します。 また、クライアントをサーバーに接続するには、WebSocket または SignalR を使用します。 これらの機能は、実行時間の長い要求を効率的に処理するように設計されています。