ASP.NET Core でファイルをアップロードするUpload files in ASP.NET Core

投稿者: Luke LathamSteve SmithRutger StormBy Luke Latham, Steve Smith, and Rutger Storm

ASP.NET Core では、小さいファイルの場合はバッファー モデル バインドを使用し、大きいファイルの場合は非バッファー ストリーミングを使用して、1 つ以上のファイルのアップロードがサポートされています。ASP.NET Core supports uploading one or more files using buffered model binding for smaller files and unbuffered streaming for larger files.

サンプル コードを表示またはダウンロードします (ダウンロード方法)。View or download sample code (how to download)

セキュリティの考慮事項Security considerations

サーバーにファイルをアップロードする機能をユーザーに提供するときは、十分に注意してください。Use caution when providing users with the ability to upload files to a server. 攻撃者が次のようなことを試みる可能性があります。Attackers may attempt to:

  • サービス拒否攻撃を実行する。Execute denial of service attacks.
  • ウイルスまたはマルウェアをアップロードする。Upload viruses or malware.
  • ネットワークやサーバーを他の方法で侵害する。Compromise networks and servers in other ways.

攻撃の成功の可能性を少なくするセキュリティ手順は、次のとおりです。Security steps that reduce the likelihood of a successful attack are:

  • 専用のファイル アップロード領域 (できれば、システム ドライブ以外) にファイルをアップロードします。Upload files to a dedicated file upload area, preferably to a non-system drive. 専用の場所を使用すると、アップロードされるファイルにセキュリティ制限を適用しやすくなります。A dedicated location makes it easier to impose security restrictions on uploaded files. ファイルのアップロード場所に対する実行アクセス許可を無効にします。†Disable execute permissions on the file upload location.†
  • アプリと同じディレクトリ ツリーに、アップロードしたファイルを保持しないでください。†Do not persist uploaded files in the same directory tree as the app.†
  • アプリによって決められた安全なファイル名を使用します。Use a safe file name determined by the app. ユーザーによって指定されたファイル名や、アップロードされるファイルの信頼されていないファイル名は、使用しないでください。†信頼されていないファイル名を表示時に HTML エンコードします。Don't use a file name provided by the user or the untrusted file name of the uploaded file.† HTML encode the untrusted file name when displaying it. たとえば、ファイル名をログに記録したり、UI に表示したりします (Razor では、出力が自動的に HTML エンコードされます)。For example, logging the file name or displaying in UI (Razor automatically HTML encodes output).
  • アプリの設計仕様に対して承認されているファイル拡張子のみを許可します。†Allow only approved file extensions for the app's design specification.†
  • クライアント側のチェックがサーバーで実行されることを確認します。†クライアント側のチェックは簡単に回避できます。Verify that client-side checks are performed on the server.† Client-side checks are easy to circumvent.
  • アップロードされたファイルのサイズをチェックします。Check the size of an uploaded file. サイズの大きなアップロードを防ぐために、最大サイズ制限を設定します。†Set a maximum size limit to prevent large uploads.†
  • 同じ名前でアップロードされたファイルによってファイルが上書きされないようにする必要があるときは、ファイルをアップロードする前に、データベースまたは物理ストレージに対してファイル名を確認します。When files shouldn't be overwritten by an uploaded file with the same name, check the file name against the database or physical storage before uploading the file.
  • ファイルを格納する前に、アップロードされる内容に対してウイルス/マルウェア スキャナーを実行します。Run a virus/malware scanner on uploaded content before the file is stored.

†サンプル アプリで、条件を満たす方法が示されています。†The sample app demonstrates an approach that meets the criteria.

警告

システムへの悪意のあるコードのアップロードは、頻繁に次のような内容のコードの実行するための足がかりとなります。Uploading malicious code to a system is frequently the first step to executing code that can:

  • システムの制御を完全に掌握する。Completely gain control of a system.
  • システムがクラッシュする結果で、システムを過負荷状態にする。Overload a system with the result that the system crashes.
  • ユーザーまたはシステムのデータを破壊する。Compromise user or system data.
  • パブリック UI に落書きする。Apply graffiti to a public UI.

ユーザーからファイルを受け入れる際の外部アクセスによる攻撃を減らす方法については、次の資料を参照してください。For information on reducing the attack surface area when accepting files from users, see the following resources:

サンプル アプリの例など、セキュリティ対策の実装の詳細については、「検証」セクションを参照してください。For more information on implementing security measures, including examples from the sample app, see the Validation section.

ストレージのシナリオStorage scenarios

ファイルの一般的なストレージ オプションには次のようなものがあります。Common storage options for files include:

  • データベースDatabase

    • 小さいファイルをアップロードする場合、物理ストレージ (ファイル システムまたはネットワーク共有) のオプションよりデータベースの方が速いことがよくあります。For small file uploads, a database is often faster than physical storage (file system or network share) options.
    • 多くの場合、ユーザー データに対するデータベース レコードの取得でファイルの内容を同時に提供できるため (たとえば、アバター イメージ)、データベースの方が物理的なストレージ オプションより便利です。A database is often more convenient than physical storage options because retrieval of a database record for user data can concurrently supply the file content (for example, an avatar image).
    • データベースは、データ ストレージ サービスを使用するよりコストが低くなる可能性があります。A database is potentially less expensive than using a data storage service.
  • 物理ストレージ (ファイル システムまたはネットワーク共有)Physical storage (file system or network share)

    • 大きいファイルのアップロードの場合:For large file uploads:
      • データベースの制限によって、アップロードのサイズが制限される場合があります。Database limits may restrict the size of the upload.
      • 多くの場合、物理ストレージはデータベース内のストレージより高コストです。Physical storage is often less economical than storage in a database.
    • 物理ストレージは、データ ストレージ サービスを使用するよりコストが低くなる可能性があります。Physical storage is potentially less expensive than using a data storage service.
    • アプリのプロセスには、ストレージの場所に対する読み取りと書き込みのアクセス許可が必要です。The app's process must have read and write permissions to the storage location. 実行アクセス許可は付与しないでください。Never grant execute permission.
  • データ ストレージ サービス (例: Azure Blob Storage)Data storage service (for example, Azure Blob Storage)

    • 通常、サービスでは、大抵の場合に単一障害点となるオンプレミス ソリューションより高いスケーラビリティと回復性が提供されます。Services usually offer improved scalability and resiliency over on-premises solutions that are usually subject to single points of failure.
    • 大規模なストレージ インフラストラクチャのシナリオでは、サービスのコストが低下する可能性があります。Services are potentially lower cost in large storage infrastructure scenarios.

    詳細については、クイック スタート:.NET を使用してオブジェクト ストレージに BLOB を作成する方法に関する記事を参照してください。For more information, see Quickstart: Use .NET to create a blob in object storage.

ファイル アップロードのシナリオFile upload scenarios

ファイルをアップロードするための一般的な 2 つの方法は、バッファーリングとストリーミングです。Two general approaches for uploading files are buffering and streaming.

バッファーリングBuffering

ファイル全体が IFormFile に読み込まれます。これは、ファイルの処理または保存に使用される C# でのファイルの表現です。The entire file is read into an IFormFile, which is a C# representation of the file used to process or save the file.

ファイルのアップロードで使用されるリソース (ディスク、メモリM) は、同時ファイル アップロードの数とサイズによって異なります。The resources (disk, memory) used by file uploads depend on the number and size of concurrent file uploads. アプリであまり多くのアップロードをバッファーに格納しようとすると、メモリまたはディスク領域が不足したときにサイトがクラッシュします。If an app attempts to buffer too many uploads, the site crashes when it runs out of memory or disk space. ファイルのアップロードのサイズまたは頻度によりアプリのリソースが不足する場合は、ストリーミングを使用します。If the size or frequency of file uploads is exhausting app resources, use streaming.

注意

1 つで 64 KB を超えるバッファー ファイルは、メモリからディスク上の一時ファイルに移動されます。Any single buffered file exceeding 64 KB is moved from memory to a temp file on disk.

小さいファイルのバッファーリングについては、後のセクションで説明します。Buffering small files is covered in the following sections of this topic:

ストリーミングStreaming

ファイルはマルチパート要求から受信され、アプリによって直接処理または保存されます。The file is received from a multipart request and directly processed or saved by the app. ストリーミングによってパフォーマンスが大幅に向上することはありません。Streaming doesn't improve performance significantly. ストリーミングを使用すると、ファイルをアップロードするときのメモリまたはディスク領域の需要を減らすことができます。Streaming reduces the demands for memory or disk space when uploading files.

大きいファイルのストリーミングについては、「ストリーミングを使用して大きいファイルをアップロードする」で説明します。Streaming large files is covered in the Upload large files with streaming section.

バッファー モデル バインドを使用して小さいファイルを物理ストレージにアップロードするUpload small files with buffered model binding to physical storage

小さいファイルをアップロードするには、マルチパート形式を使用するか、または JavaScript を使用して POST 要求を作成します。To upload small files, use a multipart form or construct a POST request using JavaScript.

次の例では、Razor Pages フォームを使用して 1 つのファイル (サンプル アプリのPages/BufferedSingleFileUploadPhysical.cshtml) をアップロードする方法を示します。The following example demonstrates the use of a Razor Pages form to upload a single file (Pages/BufferedSingleFileUploadPhysical.cshtml in the sample app):

<form enctype="multipart/form-data" method="post">
    <dl>
        <dt>
            <label asp-for="FileUpload.FormFile"></label>
        </dt>
        <dd>
            <input asp-for="FileUpload.FormFile" type="file">
            <span asp-validation-for="FileUpload.FormFile"></span>
        </dd>
    </dl>
    <input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>

次の例は前の例と似ていますが、以下の点が異なります。The following example is analogous to the prior example except that:

  • JavaScript の (Fetch API) を使用して、フォームのデータを送信します。JavaScript's (Fetch API) is used to submit the form's data.
  • 検証は行われません。There's no validation.
<form action="BufferedSingleFileUploadPhysical/?handler=Upload" 
      enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;" 
      method="post">
    <dl>
        <dt>
            <label for="FileUpload_FormFile">File</label>
        </dt>
        <dd>
            <input id="FileUpload_FormFile" type="file" 
                name="FileUpload.FormFile" />
        </dd>
    </dl>

    <input class="btn" type="submit" value="Upload" />

    <div style="margin-top:15px">
        <output name="result"></output>
    </div>
</form>

<script>
  async function AJAXSubmit (oFormElement) {
    var resultElement = oFormElement.elements.namedItem("result");
    const formData = new FormData(oFormElement);

    try {
    const response = await fetch(oFormElement.action, {
      method: 'POST',
      body: formData
    });

    if (response.ok) {
      window.location.href = '/';
    }

    resultElement.value = 'Result: ' + response.status + ' ' + 
      response.statusText;
    } catch (error) {
      console.error('Error:', error);
    }
  }
</script>

Fetch API がサポートされていないクライアントに対して JavaScript でフォーム POST を実行するには、次のいずれかの方法を使用します。To perform the form POST in JavaScript for clients that don't support the Fetch API, use one of the following approaches:

  • Fetch Polyfill を使用します (例: window.fetch polyfill (github/fetch))。Use a Fetch Polyfill (for example, window.fetch polyfill (github/fetch)).

  • XMLHttpRequest を使用してください。Use XMLHttpRequest. 次に例を示します。For example:

    <script>
      "use strict";
    
      function AJAXSubmit (oFormElement) {
        var oReq = new XMLHttpRequest();
        oReq.onload = function(e) { 
        oFormElement.elements.namedItem("result").value = 
          'Result: ' + this.status + ' ' + this.statusText;
        };
        oReq.open("post", oFormElement.action);
        oReq.send(new FormData(oFormElement));
      }
    </script>
    

ファイルのアップロードをサポートするには、HTML フォームで multipart/form-data のエンコード タイプ (enctype) を指定する必要があります。In order to support file uploads, HTML forms must specify an encoding type (enctype) of multipart/form-data.

files 入力要素で複数のファイルのアップロードをサポートするには、<input> 要素で multiple 属性を指定します。For a files input element to support uploading multiple files provide the multiple attribute on the <input> element:

<input asp-for="FileUpload.FormFiles" type="file" multiple>

サーバーにアップロードされた個々のファイルには、IFormFile を使用してモデル バインドでアクセスできます。The individual files uploaded to the server can be accessed through Model Binding using IFormFile. サンプル アプリでは、データベースおよび物理ストレージのシナリオでの複数のバッファー ファイル アップロードが示されています。The sample app demonstrates multiple buffered file uploads for database and physical storage scenarios.

警告

IFormFileFileName プロパティは、表示とログ記録の目的以外に使用しないでくださいDo not use the FileName property of IFormFile other than for display and logging. 表示またはログ記録を行うときに、ファイル名を HTML エンコードします。When displaying or logging, HTML encode the file name. 攻撃者は、完全パスまたは相対パスを含む悪意のあるファイル名を提供することがあります。An attacker can provide a malicious filename, including full paths or relative paths. アプリケーションで次の処理を行う必要があります。Applications should:

  • ユーザーが指定したファイル名からパスを削除します。Remove the path from the user-supplied filename.
  • UI またはログ記録のために、HTML エンコードされ、パスが削除されたファイル名を保存します。Save the HTML-encoded, path-removed filename for UI or logging.
  • ストレージ用に新しいランダムなファイル名を生成します。Generate a new random filename for storage.

次のコードでは、ファイル名からパスを削除します。The following code removes the path from the file name:

string untrustedFileName = Path.GetFileName(pathName);

これまでに示した例では、セキュリティ上の考慮事項については考えられていません。The examples provided thus far don't take into account security considerations. 以下のセクションおよびサンプル アプリで、追加の情報が提供されています。Additional information is provided by the following sections and the sample app:

モデル バインドと IFormFile を使用してファイルをアップロードする場合、アクション メソッドでは以下を受け入れることができます。When uploading files using model binding and IFormFile, the action method can accept:

注意

バインドでは、名前でフォーム ファイルが照合されます。Binding matches form files by name. たとえば、HTML の <input type="file" name="formFile">name の値は、バインドされた C# のパラメーター/プロパティと一致する必要があります (FormFile)。For example, the HTML name value in <input type="file" name="formFile"> must match the C# parameter/property bound (FormFile). 詳細については、「name 属性の値を POST メソッドのパラメーター名に一致させる」を参照してください。For more information, see the Match name attribute value to parameter name of POST method section.

次のような例です。The following example:

  • アップロードされた 1 つ以上のファイルをループします。Loops through one or more uploaded files.
  • Path.GetTempFileName 使用して、ファイル名を含むファイルの完全なパスを返します。Uses Path.GetTempFileName to return a full path for a file, including the file name.
  • アプリによって生成されたファイル名を使用して、ローカル ファイル システムにファイルを保存します。Saves the files to the local file system using a file name generated by the app.
  • アップロードされたファイルの合計数とサイズを返します。Returns the total number and size of files uploaded.
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
    long size = files.Sum(f => f.Length);

    foreach (var formFile in files)
    {
        if (formFile.Length > 0)
        {
            var filePath = Path.GetTempFileName();

            using (var stream = System.IO.File.Create(filePath))
            {
                await formFile.CopyToAsync(stream);
            }
        }
    }

    // Process uploaded files
    // Don't rely on or trust the FileName property without validation.

    return Ok(new { count = files.Count, size, filePath });
}

パスを除いてファイル名を生成するには、Path.GetRandomFileName を使用します。Use Path.GetRandomFileName to generate a file name without a path. 次の例では、構成からパスを取得します。In the following example, the path is obtained from configuration:

foreach (var formFile in files)
{
    if (formFile.Length > 0)
    {
        var filePath = Path.Combine(_config["StoredFilesPath"], 
            Path.GetRandomFileName());

        using (var stream = System.IO.File.Create(filePath))
        {
            await formFile.CopyToAsync(stream);
        }
    }
}

FileStream に渡すパスには、ファイル名が含まれている "必要があります"。The path passed to the FileStream must include the file name. ファイル名を指定しないと、実行時に UnauthorizedAccessException がスローされます。If the file name isn't provided, an UnauthorizedAccessException is thrown at runtime.

IFormFile の方法を使用してアップロードされたファイルは、処理の前に、サーバー上のメモリまたはディスクのバッファーに格納されます。Files uploaded using the IFormFile technique are buffered in memory or on disk on the server before processing. アクション メソッド内では、IFormFile の内容には Stream としてアクセスできます。Inside the action method, the IFormFile contents are accessible as a Stream. ローカル ファイル システムに加えて、ネットワーク共有またはファイル ストレージ サービス (Azure Blob Storage など) にファイルを保存することができます。In addition to the local file system, files can be saved to a network share or to a file storage service, such as Azure Blob storage.

アップロードのために複数のファイルをループし、安全なファイル名を使用する別の例については、サンプル アプリの Pages/BufferedMultipleFileUploadPhysical.cshtml.cs を参照してください。For another example that loops over multiple files for upload and uses safe file names, see Pages/BufferedMultipleFileUploadPhysical.cshtml.cs in the sample app.

警告

以前の一時ファイルを削除せずに、65,535 個より多くのファイルを作成すると、Path.GetTempFileNameIOException がスローされます。Path.GetTempFileName throws an IOException if more than 65,535 files are created without deleting previous temporary files. 65,535 ファイルの制限は、サーバーごとの制限です。The limit of 65,535 files is a per-server limit. Windows OS でのこの制限の詳細については、次のトピックの「解説」を参照してください。For more information on this limit on Windows OS, see the remarks in the following topics:

バッファー モデル バインドを使用して小さいファイルをデータベースにアップロードするUpload small files with buffered model binding to a database

Entity Framework を使用してデータベースにバイナリ ファイル データを格納するには、エンティティで Byte 配列プロパティを定義します。To store binary file data in a database using Entity Framework, define a Byte array property on the entity:

public class AppFile
{
    public int Id { get; set; }
    public byte[] Content { get; set; }
}

IFormFile が含まれるクラスに対してページ モデル プロパティを指定します。Specify a page model property for the class that includes an IFormFile:

public class BufferedSingleFileUploadDbModel : PageModel
{
    ...

    [BindProperty]
    public BufferedSingleFileUploadDb FileUpload { get; set; }

    ...
}

public class BufferedSingleFileUploadDb
{
    [Required]
    [Display(Name="File")]
    public IFormFile FormFile { get; set; }
}

注意

IFormFile は、アクション メソッドのパラメーターとして直接、またはバインドされたモデル プロパティとして、使用することができます。IFormFile can be used directly as an action method parameter or as a bound model property. 前の例では、バインドされたモデル プロパティが使用されています。The prior example uses a bound model property.

FileUpload は、Razor Pages フォームで使用されます。The FileUpload is used in the Razor Pages form:

<form enctype="multipart/form-data" method="post">
    <dl>
        <dt>
            <label asp-for="FileUpload.FormFile"></label>
        </dt>
        <dd>
            <input asp-for="FileUpload.FormFile" type="file">
        </dd>
    </dl>
    <input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>

フォームがサーバーに POST されるときに、IFormFile をストリームにコピーし、バイト配列としてデータベースに保存します。When the form is POSTed to the server, copy the IFormFile to a stream and save it as a byte array in the database. 次の例では、_dbContext によってアプリのデータベース コンテキストが格納されます。In the following example, _dbContext stores the app's database context:

public async Task<IActionResult> OnPostUploadAsync()
{
    using (var memoryStream = new MemoryStream())
    {
        await FileUpload.FormFile.CopyToAsync(memoryStream);

        // Upload the file if less than 2 MB
        if (memoryStream.Length < 2097152)
        {
            var file = new AppFile()
            {
                Content = memoryStream.ToArray()
            };

            _dbContext.File.Add(file);

            await _dbContext.SaveChangesAsync();
        }
        else
        {
            ModelState.AddModelError("File", "The file is too large.");
        }
    }

    return Page();
}

前の例は、次のサンプル アプリで示されているシナリオに似ています。The preceding example is similar to a scenario demonstrated in the sample app:

  • Pages/BufferedSingleFileUploadDb.cshtmlPages/BufferedSingleFileUploadDb.cshtml
  • Pages/BufferedSingleFileUploadDb.cshtml.csPages/BufferedSingleFileUploadDb.cshtml.cs

警告

パフォーマンスに悪影響を与える可能性があるため、リレーショナル データベースにバイナリ データを格納する場合は注意してください。Use caution when storing binary data in relational databases, as it can adversely impact performance.

検証を行わずに IFormFileFileName プロパティに依存したり、信頼したりしないでください。Don't rely on or trust the FileName property of IFormFile without validation. FileName プロパティは、表示目的でのみ、HTML エンコードした後でだけ、使用する必要があります。The FileName property should only be used for display purposes and only after HTML encoding.

示した例では、セキュリティ上の考慮事項については考えられていません。The examples provided don't take into account security considerations. 以下のセクションおよびサンプル アプリで、追加の情報が提供されています。Additional information is provided by the following sections and the sample app:

ストリーミングを使用して大きいファイルをアップロードするUpload large files with streaming

次の例では、JavaScript を使用してファイルをコントローラーのアクションにストリーミングする方法を示します。The following example demonstrates how to use JavaScript to stream a file to a controller action. ファイルの偽造防止トークンは、カスタム フィルター属性を使用して生成され、要求本文ではなくクライアント HTTP ヘッダーに渡されます。The file's antiforgery token is generated using a custom filter attribute and passed to the client HTTP headers instead of in the request body. アクション メソッドではアップロードされたデータが直接処理されるため、フォーム モデル バインドは別のカスタム フィルターでは無効になります。Because the action method processes the uploaded data directly, form model binding is disabled by another custom filter. アクション内では、フォームのコンテンツが MultipartReader を使用して読み取られます。その場合、各 MultipartSection が読み取られ、必要に応じて、ファイルが処理されるかコンテンツが格納されます。Within the action, the form's contents are read using a MultipartReader, which reads each individual MultipartSection, processing the file or storing the contents as appropriate. マルチパート セクションが読み取られた後、アクションで独自のモデル バインドが実行されます。After the multipart sections are read, the action performs its own model binding.

最初のページ応答ではフォームが読み込まれ、Cookie に偽造防止トークンが保存されます (GenerateAntiforgeryTokenCookieAttribute 属性を使用)。The initial page response loads the form and saves an antiforgery token in a cookie (via the GenerateAntiforgeryTokenCookieAttribute attribute). その属性では、ASP.NET Core の組み込みの偽造防止サポートを使用して、要求トークンで Cookie が設定されます。The attribute uses ASP.NET Core's built-in antiforgery support to set a cookie with a request token:

public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();

        // Send the request token as a JavaScript-readable cookie
        var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);

        context.HttpContext.Response.Cookies.Append(
            "RequestVerificationToken",
            tokens.RequestToken,
            new CookieOptions() { HttpOnly = false });
    }

    public override void OnResultExecuted(ResultExecutedContext context)
    {
    }
}

DisableFormValueModelBindingAttribute は、モデル バインドを無効にするために使用されます。The DisableFormValueModelBindingAttribute is used to disable model binding:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        var factories = context.ValueProviderFactories;
        factories.RemoveType<FormValueProviderFactory>();
        factories.RemoveType<FormFileValueProviderFactory>();
        factories.RemoveType<JQueryFormValueProviderFactory>();
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

サンプル アプリでは、GenerateAntiforgeryTokenCookieAttribute および DisableFormValueModelBindingAttribute は、Razor Pages の規則を使用して、Startup.ConfigureServices/StreamedSingleFileUploadDb および /StreamedSingleFileUploadPhysical のページ アプリケーション モデルにフィルターとして適用されます。In the sample app, GenerateAntiforgeryTokenCookieAttribute and DisableFormValueModelBindingAttribute are applied as filters to the page application models of /StreamedSingleFileUploadDb and /StreamedSingleFileUploadPhysical in Startup.ConfigureServices using Razor Pages conventions:

services.AddRazorPages()
    .AddRazorPagesOptions(options =>
        {
            options.Conventions
                .AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
                    model =>
                    {
                        model.Filters.Add(
                            new GenerateAntiforgeryTokenCookieAttribute());
                        model.Filters.Add(
                            new DisableFormValueModelBindingAttribute());
                    });
            options.Conventions
                .AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
                    model =>
                    {
                        model.Filters.Add(
                            new GenerateAntiforgeryTokenCookieAttribute());
                        model.Filters.Add(
                            new DisableFormValueModelBindingAttribute());
                    });
        });

モデル バインドではフォームが読み取られないため、フォームからバインドされているパラメーターはバインドされません (クエリ、ルート、ヘッダーは引き続き機能します)。Since model binding doesn't read the form, parameters that are bound from the form don't bind (query, route, and header continue to work). アクション メソッドでは、Request プロパティが直接操作されます。The action method works directly with the Request property. MultipartReader は各セクションを読み取るために使用されます。A MultipartReader is used to read each section. キー/値データは KeyValueAccumulator に格納されます。Key/value data is stored in a KeyValueAccumulator. マルチパート セクションが読み取られた後、KeyValueAccumulator の内容を使用して、フォーム データがモデル タイプにバインドされます。After the multipart sections are read, the contents of the KeyValueAccumulator are used to bind the form data to a model type.

EF Core でデータベースにストリーミングするための完全な StreamingController.UploadDatabase メソッド:The complete StreamingController.UploadDatabase method for streaming to a database with EF Core:

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
    {
        ModelState.AddModelError("File", 
            $"The request couldn't be processed (Error 1).");
        // Log error

        return BadRequest(ModelState);
    }

    // Accumulate the form data key-value pairs in the request (formAccumulator).
    var formAccumulator = new KeyValueAccumulator();
    var trustedFileNameForDisplay = string.Empty;
    var untrustedFileNameForStorage = string.Empty;
    var streamedFileContent = new byte[0];

    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        _defaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, HttpContext.Request.Body);

    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    {
        var hasContentDispositionHeader = 
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out var contentDisposition);

        if (hasContentDispositionHeader)
        {
            if (MultipartRequestHelper
                .HasFileContentDisposition(contentDisposition))
            {
                untrustedFileNameForStorage = contentDisposition.FileName.Value;
                // Don't trust the file name sent by the client. To display
                // the file name, HTML-encode the value.
                trustedFileNameForDisplay = WebUtility.HtmlEncode(
                        contentDisposition.FileName.Value);

                streamedFileContent = 
                    await FileHelpers.ProcessStreamedFile(section, contentDisposition, 
                        ModelState, _permittedExtensions, _fileSizeLimit);

                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }
            }
            else if (MultipartRequestHelper
                .HasFormDataContentDisposition(contentDisposition))
            {
                // Don't limit the key name length because the 
                // multipart headers length limit is already in effect.
                var key = HeaderUtilities
                    .RemoveQuotes(contentDisposition.Name).Value;
                var encoding = GetEncoding(section);

                if (encoding == null)
                {
                    ModelState.AddModelError("File", 
                        $"The request couldn't be processed (Error 2).");
                    // Log error

                    return BadRequest(ModelState);
                }

                using (var streamReader = new StreamReader(
                    section.Body,
                    encoding,
                    detectEncodingFromByteOrderMarks: true,
                    bufferSize: 1024,
                    leaveOpen: true))
                {
                    // The value length limit is enforced by 
                    // MultipartBodyLengthLimit
                    var value = await streamReader.ReadToEndAsync();

                    if (string.Equals(value, "undefined", 
                        StringComparison.OrdinalIgnoreCase))
                    {
                        value = string.Empty;
                    }

                    formAccumulator.Append(key, value);

                    if (formAccumulator.ValueCount > 
                        _defaultFormOptions.ValueCountLimit)
                    {
                        // Form key count limit of 
                        // _defaultFormOptions.ValueCountLimit 
                        // is exceeded.
                        ModelState.AddModelError("File", 
                            $"The request couldn't be processed (Error 3).");
                        // Log error

                        return BadRequest(ModelState);
                    }
                }
            }
        }

        // Drain any remaining section body that hasn't been consumed and
        // read the headers for the next section.
        section = await reader.ReadNextSectionAsync();
    }

    // Bind form data to the model
    var formData = new FormData();
    var formValueProvider = new FormValueProvider(
        BindingSource.Form,
        new FormCollection(formAccumulator.GetResults()),
        CultureInfo.CurrentCulture);
    var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
        valueProvider: formValueProvider);

    if (!bindingSuccessful)
    {
        ModelState.AddModelError("File", 
            "The request couldn't be processed (Error 5).");
        // Log error

        return BadRequest(ModelState);
    }

    // **WARNING!**
    // In the following example, the file is saved without
    // scanning the file's contents. In most production
    // scenarios, an anti-virus/anti-malware scanner API
    // is used on the file before making the file available
    // for download or for use by other systems. 
    // For more information, see the topic that accompanies 
    // this sample app.

    var file = new AppFile()
    {
        Content = streamedFileContent,
        UntrustedName = untrustedFileNameForStorage,
        Note = formData.Note,
        Size = streamedFileContent.Length, 
        UploadDT = DateTime.UtcNow
    };

    _context.File.Add(file);
    await _context.SaveChangesAsync();

    return Created(nameof(StreamingController), null);
}

MultipartRequestHelper (Utilities/MultipartRequestHelper.cs):MultipartRequestHelper (Utilities/MultipartRequestHelper.cs):

using System;
using System.IO;
using Microsoft.Net.Http.Headers;

namespace SampleApp.Utilities
{
    public static class MultipartRequestHelper
    {
        // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
        // The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
        public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
        {
            var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;

            if (string.IsNullOrWhiteSpace(boundary))
            {
                throw new InvalidDataException("Missing content-type boundary.");
            }

            if (boundary.Length > lengthLimit)
            {
                throw new InvalidDataException(
                    $"Multipart boundary length limit {lengthLimit} exceeded.");
            }

            return boundary;
        }

        public static bool IsMultipartContentType(string contentType)
        {
            return !string.IsNullOrEmpty(contentType)
                   && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
        }

        public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
        {
            // Content-Disposition: form-data; name="key";
            return contentDisposition != null
                && contentDisposition.DispositionType.Equals("form-data")
                && string.IsNullOrEmpty(contentDisposition.FileName.Value)
                && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
        }

        public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
        {
            // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
            return contentDisposition != null
                && contentDisposition.DispositionType.Equals("form-data")
                && (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
                    || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
        }
    }
}

物理的な場所にストリーミングするための完全な StreamingController.UploadPhysical メソッド:The complete StreamingController.UploadPhysical method for streaming to a physical location:

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
    {
        ModelState.AddModelError("File", 
            $"The request couldn't be processed (Error 1).");
        // Log error

        return BadRequest(ModelState);
    }

    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        _defaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, HttpContext.Request.Body);
    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    {
        var hasContentDispositionHeader = 
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out var contentDisposition);

        if (hasContentDispositionHeader)
        {
            // This check assumes that there's a file
            // present without form data. If form data
            // is present, this method immediately fails
            // and returns the model error.
            if (!MultipartRequestHelper
                .HasFileContentDisposition(contentDisposition))
            {
                ModelState.AddModelError("File", 
                    $"The request couldn't be processed (Error 2).");
                // Log error

                return BadRequest(ModelState);
            }
            else
            {
                // Don't trust the file name sent by the client. To display
                // the file name, HTML-encode the value.
                var trustedFileNameForDisplay = WebUtility.HtmlEncode(
                        contentDisposition.FileName.Value);
                var trustedFileNameForFileStorage = Path.GetRandomFileName();

                // **WARNING!**
                // In the following example, the file is saved without
                // scanning the file's contents. In most production
                // scenarios, an anti-virus/anti-malware scanner API
                // is used on the file before making the file available
                // for download or for use by other systems. 
                // For more information, see the topic that accompanies 
                // this sample.

                var streamedFileContent = await FileHelpers.ProcessStreamedFile(
                    section, contentDisposition, ModelState, 
                    _permittedExtensions, _fileSizeLimit);

                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }

                using (var targetStream = System.IO.File.Create(
                    Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
                {
                    await targetStream.WriteAsync(streamedFileContent);

                    _logger.LogInformation(
                        "Uploaded file '{TrustedFileNameForDisplay}' saved to " +
                        "'{TargetFilePath}' as {TrustedFileNameForFileStorage}", 
                        trustedFileNameForDisplay, _targetFilePath, 
                        trustedFileNameForFileStorage);
                }
            }
        }

        // Drain any remaining section body that hasn't been consumed and
        // read the headers for the next section.
        section = await reader.ReadNextSectionAsync();
    }

    return Created(nameof(StreamingController), null);
}

サンプル アプリでは、検証チェックは FileHelpers.ProcessStreamedFile によって処理されます。In the sample app, validation checks are handled by FileHelpers.ProcessStreamedFile.

検証Validation

サンプル アプリの FileHelpers クラスでは、バッファーリングされた IFormFile とストリーミングされたファイルのアップロードに関するいくつかのチェックが示されています。The sample app's FileHelpers class demonstrates a several checks for buffered IFormFile and streamed file uploads. サンプル アプリでのバッファーリングされたファイルのアップロード IFormFile の処理については、Utilities/FileHelpers.cs ファイルの ProcessFormFile メソッドを参照してください。For processing IFormFile buffered file uploads in the sample app, see the ProcessFormFile method in the Utilities/FileHelpers.cs file. ストリーミングされたファイルの処理については、同じファイルの ProcessStreamedFile メソッドを参照してください。For processing streamed files, see the ProcessStreamedFile method in the same file.

警告

サンプル アプリで示されている検証処理メソッドでは、アップロードされたファイルの内容はスキャンされません。The validation processing methods demonstrated in the sample app don't scan the content of uploaded files. ほとんどの運用シナリオでは、ファイルをユーザーまたは他のシステムで使用できるようにする前に、ウイルス/マルウェア スキャナー API が使用されます。In most production scenarios, a virus/malware scanner API is used on the file before making the file available to users or other systems.

このトピックのサンプルでは検証技法の実際の例が示されていますが、次の場合を除き、運用アプリでは FileHelpers クラスを実装しないでください。Although the topic sample provides a working example of validation techniques, don't implement the FileHelpers class in a production app unless you:

  • 実装を完全に理解している。Fully understand the implementation.
  • アプリの環境と仕様に合わせて実装が適切に変更されている。Modify the implementation as appropriate for the app's environment and specifications.

これらの要件に対応することなく、アプリにセキュリティ コードをむやみに実装しないでください。Never indiscriminately implement security code in an app without addressing these requirements.

コンテンツの検証Content validation

アップロードされた内容に対してサードパーティ製のウイルス/マルウェア スキャン API を使用します。Use a third party virus/malware scanning API on uploaded content.

大容量のシナリオでは、ファイルのスキャンに大量のサーバー リソースが必要です。Scanning files is demanding on server resources in high volume scenarios. ファイルのスキャンによって要求の処理パフォーマンスが低下する場合は、スキャン処理をバックグラウンド サービスにオフロードすることを検討してください (たとえば、アプリのサーバーとは異なるサーバーで実行されているサービス)。If request processing performance is diminished due to file scanning, consider offloading the scanning work to a background service, possibly a service running on a server different from the app's server. 通常、アップロードされたファイルは、バックグラウンドのウイルス検索プログラムによってチェックされるまで、検疫された領域に保持されます。Typically, uploaded files are held in a quarantined area until the background virus scanner checks them. 合格したファイルは、通常のファイル ストレージの場所に移動されます。When a file passes, the file is moved to the normal file storage location. これらの手順は、通常、ファイルのスキャン状態を示すデータベース レコードと共に実行されます。These steps are usually performed in conjunction with a database record that indicates the scanning status of a file. このような方法を使用することで、アプリとアプリ サーバーは要求への応答に集中できます。By using such an approach, the app and app server remain focused on responding to requests.

ファイル拡張子の検証File extension validation

アップロードされたファイルの拡張子を、許可されている拡張子のリストで確認する必要があります。The uploaded file's extension should be checked against a list of permitted extensions. 次に例を示します。For example:

private string[] permittedExtensions = { ".txt", ".pdf" };

var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();

if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
    // The extension is invalid ... discontinue processing the file
}

ファイルのシグネチャの検証File signature validation

ファイルのシグネチャは、ファイルの先頭にある最初の数バイトによって決定されます。A file's signature is determined by the first few bytes at the start of a file. これらのバイトを使用して、拡張子がファイルの内容と一致するかどうかを示すことができます。These bytes can be used to indicate if the extension matches the content of the file. サンプル アプリでは、いくつかの一般的なファイルの種類についてファイルのシグネチャがチェックされています。The sample app checks file signatures for a few common file types. 次の例では、JPEG イメージのファイルのシグネチャが、ファイルに対してチェックされています。In the following example, the file signature for a JPEG image is checked against the file:

private static readonly Dictionary<string, List<byte[]>> _fileSignature = 
    new Dictionary<string, List<byte[]>>
{
    { ".jpeg", new List<byte[]>
        {
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
        }
    },
};

using (var reader = new BinaryReader(uploadedFileData))
{
    var signatures = _fileSignature[ext];
    var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));

    return signatures.Any(signature => 
        headerBytes.Take(signature.Length).SequenceEqual(signature));
}

追加のファイル シグネチャを取得するには、「ファイル シグネチャ データベース」および公式なファイルの仕様を参照してください。To obtain additional file signatures, see the File Signatures Database and official file specifications.

ファイル名のセキュリティFile name security

ファイルを物理ストレージに保存する場合は、クライアント指定のファイル名を使用しないでください。Never use a client-supplied file name for saving a file to physical storage. Path.GetRandomFileName または Path.GetTempFileName を使用して、ファイルの安全なファイル名を作成し、一時ストレージの完全なパス (ファイル名を含む) を作成します。Create a safe file name for the file using Path.GetRandomFileName or Path.GetTempFileName to create a full path (including the file name) for temporary storage.

Razor では、プロパティ値が表示用に自動的に HTML エンコードされます。Razor automatically HTML encodes property values for display. 次のコードは安全に使用できます。The following code is safe to use:

@foreach (var file in Model.DatabaseFiles) {
    <tr>
        <td>
            @file.UntrustedName
        </td>
    </tr>
}

Razor の外部では、ユーザーの要求からのファイル名の内容を常に HtmlEncode でエンコードします。Outside of Razor, always HtmlEncode file name content from a user's request.

多くの実装には、ファイルが存在するかどうかのチェックを含める必要があります。そうしないと、同じ名前のファイルによってファイルが上書きされます。Many implementations must include a check that the file exists; otherwise, the file is overwritten by a file of the same name. アプリの仕様を満たす追加のロジックを提供します。Supply additional logic to meet your app's specifications.

サイズの検証Size validation

アップロードされるファイルのサイズを制限します。Limit the size of uploaded files.

サンプル アプリでは、ファイルのサイズは 2 MB (バイト単位) に制限されています。In the sample app, the size of the file is limited to 2 MB (indicated in bytes). その制限は、appsettings.json ファイルの Configuration によって提供されます。The limit is supplied via Configuration from the appsettings.json file:

{
  "FileSizeLimit": 2097152
}

FileSizeLimitPageModel クラスに挿入されます。The FileSizeLimit is injected into PageModel classes:

public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    private readonly long _fileSizeLimit;

    public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
    {
        _fileSizeLimit = config.GetValue<long>("FileSizeLimit");
    }

    ...
}

ファイルのサイズが制限を超えると、ファイルは拒否されます。When a file size exceeds the limit, the file is rejected:

if (formFile.Length > _fileSizeLimit)
{
    // The file is too large ... discontinue processing the file
}

name 属性の値を POST メソッドのパラメーター名に一致させるMatch name attribute value to parameter name of POST method

フォーム データを POST する、または JavaScript の FormData を直接使用する、Razor 以外のフォームでは、フォームの要素または FormData で指定されている名前が、コントローラーのアクションのパラメーターの name と一致している必要があります。In non-Razor forms that POST form data or use JavaScript's FormData directly, the name specified in the form's element or FormData must match the name of the parameter in the controller's action.

次に例を示します。In the following example:

  • <input> 要素を使用すると、name 属性には値 battlePlans が設定されます。When using an <input> element, the name attribute is set to the value battlePlans:

    <input type="file" name="battlePlans" multiple>
    
  • JavaScript で FormData を使用すると、name には値 battlePlans が設定されます。When using FormData in JavaScript, the name is set to the value battlePlans:

    var formData = new FormData();
    
    for (var file in files) {
      formData.append("battlePlans", file, file.name);
    }
    

C# メソッドのパラメーターに一致する名前を使用します (battlePlans)。Use a matching name for the parameter of the C# method (battlePlans):

  • Upload という名前の Razor Pages ページ ハンドラー メソッドの場合:For a Razor Pages page handler method named Upload:

    public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
    
  • MVC POST コントローラー アクション メソッドの場合:For an MVC POST controller action method:

    public async Task<IActionResult> Post(List<IFormFile> battlePlans)
    

サーバーとアプリの構成Server and app configuration

マルチパート本文の長さの制限Multipart body length limit

MultipartBodyLengthLimit では、各マルチパート本文の長さの制限が設定されます。MultipartBodyLengthLimit sets the limit for the length of each multipart body. この制限を超えるフォーム セクションでは、解析時に InvalidDataException がスローされます。Form sections that exceed this limit throw an InvalidDataException when parsed. 既定値は 134,217,728 (128 MB) です。The default is 134,217,728 (128 MB). 制限をカスタマイズするには、Startup.ConfigureServices の設定 MultipartBodyLengthLimit を使用します。Customize the limit using the MultipartBodyLengthLimit setting in Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<FormOptions>(options =>
    {
        // Set the limit to 256 MB
        options.MultipartBodyLengthLimit = 268435456;
    });
}

単一ページまたはアクションに対する MultipartBodyLengthLimit を設定するには、RequestFormLimitsAttribute を使用します。RequestFormLimitsAttribute is used to set the MultipartBodyLengthLimit for a single page or action.

Razor Pages アプリでは、Startup.ConfigureServicesconvention を使用してフィルターを適用します。In a Razor Pages app, apply the filter with a convention in Startup.ConfigureServices:

services.AddRazorPages()
    .AddRazorPagesOptions(options =>
    {
        options.Conventions
            .AddPageApplicationModelConvention("/FileUploadPage",
                model.Filters.Add(
                    new RequestFormLimitsAttribute()
                    {
                        // Set the limit to 256 MB
                        MultipartBodyLengthLimit = 268435456
                    });
    });

Razor Pages アプリまたは MVC アプリでは、ページ モデルまたはアクション メソッドにフィルターを適用します。In a Razor Pages app or an MVC app, apply the filter to the page model or action method:

// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    ...
}

Kestrel の最大要求本文サイズKestrel maximum request body size

Kestrel によってホストされるアプリの場合、既定の要求本文の最大サイズは、30,000,000 バイトです。これは約 28.6 MB になります。For apps hosted by Kestrel, the default maximum request body size is 30,000,000 bytes, which is approximately 28.6 MB. 制限をカスタマイズするには、MaxRequestBodySize Kestrel サーバー オプションを使用します。Customize the limit using the MaxRequestBodySize Kestrel server option:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureKestrel((context, options) =>
        {
            // Handle requests up to 50 MB
            options.Limits.MaxRequestBodySize = 52428800;
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

単一ページまたはアクションに対する MaxRequestBodySize を設定するには、RequestSizeLimitAttribute を使用します。RequestSizeLimitAttribute is used to set the MaxRequestBodySize for a single page or action.

Razor Pages アプリでは、Startup.ConfigureServicesconvention を使用してフィルターを適用します。In a Razor Pages app, apply the filter with a convention in Startup.ConfigureServices:

services.AddRazorPages()
    .AddRazorPagesOptions(options =>
    {
        options.Conventions
            .AddPageApplicationModelConvention("/FileUploadPage",
                model =>
                {
                    // Handle requests up to 50 MB
                    model.Filters.Add(
                        new RequestSizeLimitAttribute(52428800));
                });
    });

Razor Pages アプリまたは MVC アプリでは、ページ ハンドラー クラスまたはアクション メソッドにフィルターを適用します。In a Razor pages app or an MVC app, apply the filter to the page handler class or action method:

// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    ...
}

RequestSizeLimitAttribute は、@attribute Razor ディレクティブを使用して適用することもできます。The RequestSizeLimitAttribute can also be applied using the @attribute Razor directive:

@attribute [RequestSizeLimitAttribute(52428800)]

その他の Kestrel の制限Other Kestrel limits

Kestrel によってホストされるアプリには、他の Kestrel の制限が適用される場合があります。Other Kestrel limits may apply for apps hosted by Kestrel:

IIS の内容の長さの制限IIS content length limit

既定の要求の制限 (maxAllowedContentLength) は、30,000,000 バイトです。これは約 28.6 MB になります。The default request limit (maxAllowedContentLength) is 30,000,000 bytes, which is approximately 28.6MB. web.config ファイルで制限をカスタマイズします。Customize the limit in the web.config file:

<system.webServer>
  <security>
    <requestFiltering>
      <!-- Handle requests up to 50 MB -->
      <requestLimits maxAllowedContentLength="52428800" />
    </requestFiltering>
  </security>
</system.webServer>

この設定は IIS にのみ適用されます。This setting only applies to IIS. Kestrel でホストする場合、既定ではこの動作は発生しません。The behavior doesn't occur by default when hosting on Kestrel. 詳細については、「要求制限 <requestLimits>」を参照してください。For more information, see Request Limits <requestLimits>.

ASP.NET Core モジュールでの制限または IIS 要求フィルター モジュールの存在により、アップロードが 2 または 4 GB に制限される場合があります。Limitations in the ASP.NET Core Module or presence of the IIS Request Filtering Module may limit uploads to either 2 or 4 GB. 詳細については、「サイズが 2 GB を超えるファイルをアップロードできない (dotnet/AspNetCore #2711)」を参照してください。For more information, see Unable to upload file greater than 2GB in size (dotnet/AspNetCore #2711).

トラブルシューティングTroubleshoot

ファイルのアップロード時に発生する一般的ないくつかの問題と、考えられる解決策を以下に示します。Below are some common problems encountered when working with uploading files and their possible solutions.

IIS サーバーに展開したときの "見つかりません" エラーNot Found error when deployed to an IIS server

次のエラーは、アップロードされるファイルがサーバーの構成されている内容の長さを超えていることを示します。The following error indicates that the uploaded file exceeds the server's configured content length:

HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.

制限を増やす方法の詳細については、「IIS の内容の長さの制限」セクションを参照してください。For more information on increasing the limit, see the IIS content length limit section.

接続エラーConnection failure

接続エラーおよびサーバー接続のリセットは、アップロードされるファイルが Kestrel の最大要求本文サイズを超えていることを示している場合があります。A connection error and a reset server connection probably indicates that the uploaded file exceeds Kestrel's maximum request body size. 詳細については、Kestrel の最大要求本文サイズ」セクションを参照してください。For more information, see the Kestrel maximum request body size section. Kestrel クライアント接続の制限の調整も必要な場合があります。Kestrel client connection limits may also require adjustment.

IFormFile での null 参照例外Null Reference Exception with IFormFile

コントローラーが IFormFile を使用してアップロードされたファイルを受け取っても、値が null の場合は、HTML フォームで multipart/form-data に値 enctype が指定されていることを確認します。If the controller is accepting uploaded files using IFormFile but the value is null, confirm that the HTML form is specifying an enctype value of multipart/form-data. この属性が <form> 要素で設定されていない場合、ファイルはアップロードされず、バインドされた IFormFile 引数は null になります。If this attribute isn't set on the <form> element, the file upload doesn't occur and any bound IFormFile arguments are null. また、フォーム データでのアップロードの名前がアプリの名前と一致することを確認します。Also confirm that the upload naming in form data matches the app's naming.

ストリームが長すぎるStream was too long

このトピックの例は、アップロードされたファイル コンテンツを保持するために MemoryStream に依存しています。The examples in this topic rely upon MemoryStream to hold the uploaded file's content. int.MaxValue のサイズ制限は MemoryStream です。The size limit of a MemoryStream is int.MaxValue. アプリのファイル アップロード シナリオで 50 MB を超えるファイル コンテンツを保持する必要がある場合は、アップロードされたファイルのコンテンツを 1 つの MemoryStream に依存することなく保持する別のアプローチを使用します。If the app's file upload scenario requires holding file content larger than 50 MB, use an alternative approach that doesn't rely upon a single MemoryStream for holding an uploaded file's content.

ASP.NET Core では、小さいファイルの場合はバッファー モデル バインドを使用し、大きいファイルの場合は非バッファー ストリーミングを使用して、1 つ以上のファイルのアップロードがサポートされています。ASP.NET Core supports uploading one or more files using buffered model binding for smaller files and unbuffered streaming for larger files.

サンプル コードを表示またはダウンロードします (ダウンロード方法)。View or download sample code (how to download)

セキュリティの考慮事項Security considerations

サーバーにファイルをアップロードする機能をユーザーに提供するときは、十分に注意してください。Use caution when providing users with the ability to upload files to a server. 攻撃者が次のようなことを試みる可能性があります。Attackers may attempt to:

  • サービス拒否攻撃を実行する。Execute denial of service attacks.
  • ウイルスまたはマルウェアをアップロードする。Upload viruses or malware.
  • ネットワークやサーバーを他の方法で侵害する。Compromise networks and servers in other ways.

攻撃の成功の可能性を少なくするセキュリティ手順は、次のとおりです。Security steps that reduce the likelihood of a successful attack are:

  • 専用のファイル アップロード領域 (できれば、システム ドライブ以外) にファイルをアップロードします。Upload files to a dedicated file upload area, preferably to a non-system drive. 専用の場所を使用すると、アップロードされるファイルにセキュリティ制限を適用しやすくなります。A dedicated location makes it easier to impose security restrictions on uploaded files. ファイルのアップロード場所に対する実行アクセス許可を無効にします。†Disable execute permissions on the file upload location.†
  • アプリと同じディレクトリ ツリーに、アップロードしたファイルを保持しないでください。†Do not persist uploaded files in the same directory tree as the app.†
  • アプリによって決められた安全なファイル名を使用します。Use a safe file name determined by the app. ユーザーによって指定されたファイル名や、アップロードされるファイルの信頼されていないファイル名は、使用しないでください。†信頼されていないファイル名を表示時に HTML エンコードします。Don't use a file name provided by the user or the untrusted file name of the uploaded file.† HTML encode the untrusted file name when displaying it. たとえば、ファイル名をログに記録したり、UI に表示したりします (Razor では、出力が自動的に HTML エンコードされます)。For example, logging the file name or displaying in UI (Razor automatically HTML encodes output).
  • アプリの設計仕様に対して承認されているファイル拡張子のみを許可します。†Allow only approved file extensions for the app's design specification.†
  • クライアント側のチェックがサーバーで実行されることを確認します。†クライアント側のチェックは簡単に回避できます。Verify that client-side checks are performed on the server.† Client-side checks are easy to circumvent.
  • アップロードされたファイルのサイズをチェックします。Check the size of an uploaded file. サイズの大きなアップロードを防ぐために、最大サイズ制限を設定します。†Set a maximum size limit to prevent large uploads.†
  • 同じ名前でアップロードされたファイルによってファイルが上書きされないようにする必要があるときは、ファイルをアップロードする前に、データベースまたは物理ストレージに対してファイル名を確認します。When files shouldn't be overwritten by an uploaded file with the same name, check the file name against the database or physical storage before uploading the file.
  • ファイルを格納する前に、アップロードされる内容に対してウイルス/マルウェア スキャナーを実行します。Run a virus/malware scanner on uploaded content before the file is stored.

†サンプル アプリで、条件を満たす方法が示されています。†The sample app demonstrates an approach that meets the criteria.

警告

システムへの悪意のあるコードのアップロードは、頻繁に次のような内容のコードの実行するための足がかりとなります。Uploading malicious code to a system is frequently the first step to executing code that can:

  • システムの制御を完全に掌握する。Completely gain control of a system.
  • システムがクラッシュする結果で、システムを過負荷状態にする。Overload a system with the result that the system crashes.
  • ユーザーまたはシステムのデータを破壊する。Compromise user or system data.
  • パブリック UI に落書きする。Apply graffiti to a public UI.

ユーザーからファイルを受け入れる際の外部アクセスによる攻撃を減らす方法については、次の資料を参照してください。For information on reducing the attack surface area when accepting files from users, see the following resources:

サンプル アプリの例など、セキュリティ対策の実装の詳細については、「検証」セクションを参照してください。For more information on implementing security measures, including examples from the sample app, see the Validation section.

ストレージのシナリオStorage scenarios

ファイルの一般的なストレージ オプションには次のようなものがあります。Common storage options for files include:

  • データベースDatabase

    • 小さいファイルをアップロードする場合、物理ストレージ (ファイル システムまたはネットワーク共有) のオプションよりデータベースの方が速いことがよくあります。For small file uploads, a database is often faster than physical storage (file system or network share) options.
    • 多くの場合、ユーザー データに対するデータベース レコードの取得でファイルの内容を同時に提供できるため (たとえば、アバター イメージ)、データベースの方が物理的なストレージ オプションより便利です。A database is often more convenient than physical storage options because retrieval of a database record for user data can concurrently supply the file content (for example, an avatar image).
    • データベースは、データ ストレージ サービスを使用するよりコストが低くなる可能性があります。A database is potentially less expensive than using a data storage service.
  • 物理ストレージ (ファイル システムまたはネットワーク共有)Physical storage (file system or network share)

    • 大きいファイルのアップロードの場合:For large file uploads:
      • データベースの制限によって、アップロードのサイズが制限される場合があります。Database limits may restrict the size of the upload.
      • 多くの場合、物理ストレージはデータベース内のストレージより高コストです。Physical storage is often less economical than storage in a database.
    • 物理ストレージは、データ ストレージ サービスを使用するよりコストが低くなる可能性があります。Physical storage is potentially less expensive than using a data storage service.
    • アプリのプロセスには、ストレージの場所に対する読み取りと書き込みのアクセス許可が必要です。The app's process must have read and write permissions to the storage location. 実行アクセス許可は付与しないでください。Never grant execute permission.
  • データ ストレージ サービス (例: Azure Blob Storage)Data storage service (for example, Azure Blob Storage)

    • 通常、サービスでは、大抵の場合に単一障害点となるオンプレミス ソリューションより高いスケーラビリティと回復性が提供されます。Services usually offer improved scalability and resiliency over on-premises solutions that are usually subject to single points of failure.
    • 大規模なストレージ インフラストラクチャのシナリオでは、サービスのコストが低下する可能性があります。Services are potentially lower cost in large storage infrastructure scenarios.

    詳細については、クイック スタート:.NET を使用してオブジェクト ストレージに BLOB を作成する方法に関する記事を参照してください。For more information, see Quickstart: Use .NET to create a blob in object storage. そのトピックでは UploadFromFileAsync が示されていますが、Stream を使用する場合は、UploadFromStreamAsync を使用して FileStream を Blob Storage に保存することもできます。The topic demonstrates UploadFromFileAsync, but UploadFromStreamAsync can be used to save a FileStream to blob storage when working with a Stream.

ファイル アップロードのシナリオFile upload scenarios

ファイルをアップロードするための一般的な 2 つの方法は、バッファーリングとストリーミングです。Two general approaches for uploading files are buffering and streaming.

バッファーリングBuffering

ファイル全体が IFormFile に読み込まれます。これは、ファイルの処理または保存に使用される C# でのファイルの表現です。The entire file is read into an IFormFile, which is a C# representation of the file used to process or save the file.

ファイルのアップロードで使用されるリソース (ディスク、メモリM) は、同時ファイル アップロードの数とサイズによって異なります。The resources (disk, memory) used by file uploads depend on the number and size of concurrent file uploads. アプリであまり多くのアップロードをバッファーに格納しようとすると、メモリまたはディスク領域が不足したときにサイトがクラッシュします。If an app attempts to buffer too many uploads, the site crashes when it runs out of memory or disk space. ファイルのアップロードのサイズまたは頻度によりアプリのリソースが不足する場合は、ストリーミングを使用します。If the size or frequency of file uploads is exhausting app resources, use streaming.

注意

1 つで 64 KB を超えるバッファー ファイルは、メモリからディスク上の一時ファイルに移動されます。Any single buffered file exceeding 64 KB is moved from memory to a temp file on disk.

小さいファイルのバッファーリングについては、後のセクションで説明します。Buffering small files is covered in the following sections of this topic:

ストリーミングStreaming

ファイルはマルチパート要求から受信され、アプリによって直接処理または保存されます。The file is received from a multipart request and directly processed or saved by the app. ストリーミングによってパフォーマンスが大幅に向上することはありません。Streaming doesn't improve performance significantly. ストリーミングを使用すると、ファイルをアップロードするときのメモリまたはディスク領域の需要を減らすことができます。Streaming reduces the demands for memory or disk space when uploading files.

大きいファイルのストリーミングについては、「ストリーミングを使用して大きいファイルをアップロードする」で説明します。Streaming large files is covered in the Upload large files with streaming section.

バッファー モデル バインドを使用して小さいファイルを物理ストレージにアップロードするUpload small files with buffered model binding to physical storage

小さいファイルをアップロードするには、マルチパート形式を使用するか、または JavaScript を使用して POST 要求を作成します。To upload small files, use a multipart form or construct a POST request using JavaScript.

次の例では、Razor Pages フォームを使用して 1 つのファイル (サンプル アプリのPages/BufferedSingleFileUploadPhysical.cshtml) をアップロードする方法を示します。The following example demonstrates the use of a Razor Pages form to upload a single file (Pages/BufferedSingleFileUploadPhysical.cshtml in the sample app):

<form enctype="multipart/form-data" method="post">
    <dl>
        <dt>
            <label asp-for="FileUpload.FormFile"></label>
        </dt>
        <dd>
            <input asp-for="FileUpload.FormFile" type="file">
            <span asp-validation-for="FileUpload.FormFile"></span>
        </dd>
    </dl>
    <input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>

次の例は前の例と似ていますが、以下の点が異なります。The following example is analogous to the prior example except that:

  • JavaScript の (Fetch API) を使用して、フォームのデータを送信します。JavaScript's (Fetch API) is used to submit the form's data.
  • 検証は行われません。There's no validation.
<form action="BufferedSingleFileUploadPhysical/?handler=Upload" 
      enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;" 
      method="post">
    <dl>
        <dt>
            <label for="FileUpload_FormFile">File</label>
        </dt>
        <dd>
            <input id="FileUpload_FormFile" type="file" 
                name="FileUpload.FormFile" />
        </dd>
    </dl>

    <input class="btn" type="submit" value="Upload" />

    <div style="margin-top:15px">
        <output name="result"></output>
    </div>
</form>

<script>
  async function AJAXSubmit (oFormElement) {
    var resultElement = oFormElement.elements.namedItem("result");
    const formData = new FormData(oFormElement);

    try {
    const response = await fetch(oFormElement.action, {
      method: 'POST',
      body: formData
    });

    if (response.ok) {
      window.location.href = '/';
    }

    resultElement.value = 'Result: ' + response.status + ' ' + 
      response.statusText;
    } catch (error) {
      console.error('Error:', error);
    }
  }
</script>

Fetch API がサポートされていないクライアントに対して JavaScript でフォーム POST を実行するには、次のいずれかの方法を使用します。To perform the form POST in JavaScript for clients that don't support the Fetch API, use one of the following approaches:

  • Fetch Polyfill を使用します (例: window.fetch polyfill (github/fetch))。Use a Fetch Polyfill (for example, window.fetch polyfill (github/fetch)).

  • XMLHttpRequest を使用してください。Use XMLHttpRequest. 次に例を示します。For example:

    <script>
      "use strict";
    
      function AJAXSubmit (oFormElement) {
        var oReq = new XMLHttpRequest();
        oReq.onload = function(e) { 
        oFormElement.elements.namedItem("result").value = 
          'Result: ' + this.status + ' ' + this.statusText;
        };
        oReq.open("post", oFormElement.action);
        oReq.send(new FormData(oFormElement));
      }
    </script>
    

ファイルのアップロードをサポートするには、HTML フォームで multipart/form-data のエンコード タイプ (enctype) を指定する必要があります。In order to support file uploads, HTML forms must specify an encoding type (enctype) of multipart/form-data.

files 入力要素で複数のファイルのアップロードをサポートするには、<input> 要素で multiple 属性を指定します。For a files input element to support uploading multiple files provide the multiple attribute on the <input> element:

<input asp-for="FileUpload.FormFiles" type="file" multiple>

サーバーにアップロードされた個々のファイルには、IFormFile を使用してモデル バインドでアクセスできます。The individual files uploaded to the server can be accessed through Model Binding using IFormFile. サンプル アプリでは、データベースおよび物理ストレージのシナリオでの複数のバッファー ファイル アップロードが示されています。The sample app demonstrates multiple buffered file uploads for database and physical storage scenarios.

警告

IFormFileFileName プロパティは、表示とログ記録の目的以外に使用しないでくださいDo not use the FileName property of IFormFile other than for display and logging. 表示またはログ記録を行うときに、ファイル名を HTML エンコードします。When displaying or logging, HTML encode the file name. 攻撃者は、完全パスまたは相対パスを含む悪意のあるファイル名を提供することがあります。An attacker can provide a malicious filename, including full paths or relative paths. アプリケーションで次の処理を行う必要があります。Applications should:

  • ユーザーが指定したファイル名からパスを削除します。Remove the path from the user-supplied filename.
  • UI またはログ記録のために、HTML エンコードされ、パスが削除されたファイル名を保存します。Save the HTML-encoded, path-removed filename for UI or logging.
  • ストレージ用に新しいランダムなファイル名を生成します。Generate a new random filename for storage.

次のコードでは、ファイル名からパスを削除します。The following code removes the path from the file name:

string untrustedFileName = Path.GetFileName(pathName);

これまでに示した例では、セキュリティ上の考慮事項については考えられていません。The examples provided thus far don't take into account security considerations. 以下のセクションおよびサンプル アプリで、追加の情報が提供されています。Additional information is provided by the following sections and the sample app:

モデル バインドと IFormFile を使用してファイルをアップロードする場合、アクション メソッドでは以下を受け入れることができます。When uploading files using model binding and IFormFile, the action method can accept:

注意

バインドでは、名前でフォーム ファイルが照合されます。Binding matches form files by name. たとえば、HTML の <input type="file" name="formFile">name の値は、バインドされた C# のパラメーター/プロパティと一致する必要があります (FormFile)。For example, the HTML name value in <input type="file" name="formFile"> must match the C# parameter/property bound (FormFile). 詳細については、「name 属性の値を POST メソッドのパラメーター名に一致させる」を参照してください。For more information, see the Match name attribute value to parameter name of POST method section.

次のような例です。The following example:

  • アップロードされた 1 つ以上のファイルをループします。Loops through one or more uploaded files.
  • Path.GetTempFileName 使用して、ファイル名を含むファイルの完全なパスを返します。Uses Path.GetTempFileName to return a full path for a file, including the file name.
  • アプリによって生成されたファイル名を使用して、ローカル ファイル システムにファイルを保存します。Saves the files to the local file system using a file name generated by the app.
  • アップロードされたファイルの合計数とサイズを返します。Returns the total number and size of files uploaded.
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
    long size = files.Sum(f => f.Length);

    foreach (var formFile in files)
    {
        if (formFile.Length > 0)
        {
            var filePath = Path.GetTempFileName();

            using (var stream = System.IO.File.Create(filePath))
            {
                await formFile.CopyToAsync(stream);
            }
        }
    }

    // Process uploaded files
    // Don't rely on or trust the FileName property without validation.

    return Ok(new { count = files.Count, size, filePath });
}

パスを除いてファイル名を生成するには、Path.GetRandomFileName を使用します。Use Path.GetRandomFileName to generate a file name without a path. 次の例では、構成からパスを取得します。In the following example, the path is obtained from configuration:

foreach (var formFile in files)
{
    if (formFile.Length > 0)
    {
        var filePath = Path.Combine(_config["StoredFilesPath"], 
            Path.GetRandomFileName());

        using (var stream = System.IO.File.Create(filePath))
        {
            await formFile.CopyToAsync(stream);
        }
    }
}

FileStream に渡すパスには、ファイル名が含まれている "必要があります"。The path passed to the FileStream must include the file name. ファイル名を指定しないと、実行時に UnauthorizedAccessException がスローされます。If the file name isn't provided, an UnauthorizedAccessException is thrown at runtime.

IFormFile の方法を使用してアップロードされたファイルは、処理の前に、サーバー上のメモリまたはディスクのバッファーに格納されます。Files uploaded using the IFormFile technique are buffered in memory or on disk on the server before processing. アクション メソッド内では、IFormFile の内容には Stream としてアクセスできます。Inside the action method, the IFormFile contents are accessible as a Stream. ローカル ファイル システムに加えて、ネットワーク共有またはファイル ストレージ サービス (Azure Blob Storage など) にファイルを保存することができます。In addition to the local file system, files can be saved to a network share or to a file storage service, such as Azure Blob storage.

アップロードのために複数のファイルをループし、安全なファイル名を使用する別の例については、サンプル アプリの Pages/BufferedMultipleFileUploadPhysical.cshtml.cs を参照してください。For another example that loops over multiple files for upload and uses safe file names, see Pages/BufferedMultipleFileUploadPhysical.cshtml.cs in the sample app.

警告

以前の一時ファイルを削除せずに、65,535 個より多くのファイルを作成すると、Path.GetTempFileNameIOException がスローされます。Path.GetTempFileName throws an IOException if more than 65,535 files are created without deleting previous temporary files. 65,535 ファイルの制限は、サーバーごとの制限です。The limit of 65,535 files is a per-server limit. Windows OS でのこの制限の詳細については、次のトピックの「解説」を参照してください。For more information on this limit on Windows OS, see the remarks in the following topics:

バッファー モデル バインドを使用して小さいファイルをデータベースにアップロードするUpload small files with buffered model binding to a database

Entity Framework を使用してデータベースにバイナリ ファイル データを格納するには、エンティティで Byte 配列プロパティを定義します。To store binary file data in a database using Entity Framework, define a Byte array property on the entity:

public class AppFile
{
    public int Id { get; set; }
    public byte[] Content { get; set; }
}

IFormFile が含まれるクラスに対してページ モデル プロパティを指定します。Specify a page model property for the class that includes an IFormFile:

public class BufferedSingleFileUploadDbModel : PageModel
{
    ...

    [BindProperty]
    public BufferedSingleFileUploadDb FileUpload { get; set; }

    ...
}

public class BufferedSingleFileUploadDb
{
    [Required]
    [Display(Name="File")]
    public IFormFile FormFile { get; set; }
}

注意

IFormFile は、アクション メソッドのパラメーターとして直接、またはバインドされたモデル プロパティとして、使用することができます。IFormFile can be used directly as an action method parameter or as a bound model property. 前の例では、バインドされたモデル プロパティが使用されています。The prior example uses a bound model property.

FileUpload は、Razor Pages フォームで使用されます。The FileUpload is used in the Razor Pages form:

<form enctype="multipart/form-data" method="post">
    <dl>
        <dt>
            <label asp-for="FileUpload.FormFile"></label>
        </dt>
        <dd>
            <input asp-for="FileUpload.FormFile" type="file">
        </dd>
    </dl>
    <input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>

フォームがサーバーに POST されるときに、IFormFile をストリームにコピーし、バイト配列としてデータベースに保存します。When the form is POSTed to the server, copy the IFormFile to a stream and save it as a byte array in the database. 次の例では、_dbContext によってアプリのデータベース コンテキストが格納されます。In the following example, _dbContext stores the app's database context:

public async Task<IActionResult> OnPostUploadAsync()
{
    using (var memoryStream = new MemoryStream())
    {
        await FileUpload.FormFile.CopyToAsync(memoryStream);

        // Upload the file if less than 2 MB
        if (memoryStream.Length < 2097152)
        {
            var file = new AppFile()
            {
                Content = memoryStream.ToArray()
            };

            _dbContext.File.Add(file);

            await _dbContext.SaveChangesAsync();
        }
        else
        {
            ModelState.AddModelError("File", "The file is too large.");
        }
    }

    return Page();
}

前の例は、次のサンプル アプリで示されているシナリオに似ています。The preceding example is similar to a scenario demonstrated in the sample app:

  • Pages/BufferedSingleFileUploadDb.cshtmlPages/BufferedSingleFileUploadDb.cshtml
  • Pages/BufferedSingleFileUploadDb.cshtml.csPages/BufferedSingleFileUploadDb.cshtml.cs

警告

パフォーマンスに悪影響を与える可能性があるため、リレーショナル データベースにバイナリ データを格納する場合は注意してください。Use caution when storing binary data in relational databases, as it can adversely impact performance.

検証を行わずに IFormFileFileName プロパティに依存したり、信頼したりしないでください。Don't rely on or trust the FileName property of IFormFile without validation. FileName プロパティは、表示目的でのみ、HTML エンコードした後でだけ、使用する必要があります。The FileName property should only be used for display purposes and only after HTML encoding.

示した例では、セキュリティ上の考慮事項については考えられていません。The examples provided don't take into account security considerations. 以下のセクションおよびサンプル アプリで、追加の情報が提供されています。Additional information is provided by the following sections and the sample app:

ストリーミングを使用して大きいファイルをアップロードするUpload large files with streaming

次の例では、JavaScript を使用してファイルをコントローラーのアクションにストリーミングする方法を示します。The following example demonstrates how to use JavaScript to stream a file to a controller action. ファイルの偽造防止トークンは、カスタム フィルター属性を使用して生成され、要求本文ではなくクライアント HTTP ヘッダーに渡されます。The file's antiforgery token is generated using a custom filter attribute and passed to the client HTTP headers instead of in the request body. アクション メソッドではアップロードされたデータが直接処理されるため、フォーム モデル バインドは別のカスタム フィルターでは無効になります。Because the action method processes the uploaded data directly, form model binding is disabled by another custom filter. アクション内では、フォームのコンテンツが MultipartReader を使用して読み取られます。その場合、各 MultipartSection が読み取られ、必要に応じて、ファイルが処理されるかコンテンツが格納されます。Within the action, the form's contents are read using a MultipartReader, which reads each individual MultipartSection, processing the file or storing the contents as appropriate. マルチパート セクションが読み取られた後、アクションで独自のモデル バインドが実行されます。After the multipart sections are read, the action performs its own model binding.

最初のページ応答ではフォームが読み込まれ、Cookie に偽造防止トークンが保存されます (GenerateAntiforgeryTokenCookieAttribute 属性を使用)。The initial page response loads the form and saves an antiforgery token in a cookie (via the GenerateAntiforgeryTokenCookieAttribute attribute). その属性では、ASP.NET Core の組み込みの偽造防止サポートを使用して、要求トークンで Cookie が設定されます。The attribute uses ASP.NET Core's built-in antiforgery support to set a cookie with a request token:

public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();

        // Send the request token as a JavaScript-readable cookie
        var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);

        context.HttpContext.Response.Cookies.Append(
            "RequestVerificationToken",
            tokens.RequestToken,
            new CookieOptions() { HttpOnly = false });
    }

    public override void OnResultExecuted(ResultExecutedContext context)
    {
    }
}

DisableFormValueModelBindingAttribute は、モデル バインドを無効にするために使用されます。The DisableFormValueModelBindingAttribute is used to disable model binding:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        var factories = context.ValueProviderFactories;
        factories.RemoveType<FormValueProviderFactory>();
        factories.RemoveType<JQueryFormValueProviderFactory>();
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

サンプル アプリでは、GenerateAntiforgeryTokenCookieAttribute および DisableFormValueModelBindingAttribute は、Razor Pages の規則を使用して、Startup.ConfigureServices/StreamedSingleFileUploadDb および /StreamedSingleFileUploadPhysical のページ アプリケーション モデルにフィルターとして適用されます。In the sample app, GenerateAntiforgeryTokenCookieAttribute and DisableFormValueModelBindingAttribute are applied as filters to the page application models of /StreamedSingleFileUploadDb and /StreamedSingleFileUploadPhysical in Startup.ConfigureServices using Razor Pages conventions:

services.AddMvc()
    .AddRazorPagesOptions(options =>
        {
            options.Conventions
                .AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
                    model =>
                    {
                        model.Filters.Add(
                            new GenerateAntiforgeryTokenCookieAttribute());
                        model.Filters.Add(
                            new DisableFormValueModelBindingAttribute());
                    });
            options.Conventions
                .AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
                    model =>
                    {
                        model.Filters.Add(
                            new GenerateAntiforgeryTokenCookieAttribute());
                        model.Filters.Add(
                            new DisableFormValueModelBindingAttribute());
                    });
        })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

モデル バインドではフォームが読み取られないため、フォームからバインドされているパラメーターはバインドされません (クエリ、ルート、ヘッダーは引き続き機能します)。Since model binding doesn't read the form, parameters that are bound from the form don't bind (query, route, and header continue to work). アクション メソッドでは、Request プロパティが直接操作されます。The action method works directly with the Request property. MultipartReader は各セクションを読み取るために使用されます。A MultipartReader is used to read each section. キー/値データは KeyValueAccumulator に格納されます。Key/value data is stored in a KeyValueAccumulator. マルチパート セクションが読み取られた後、KeyValueAccumulator の内容を使用して、フォーム データがモデル タイプにバインドされます。After the multipart sections are read, the contents of the KeyValueAccumulator are used to bind the form data to a model type.

EF Core でデータベースにストリーミングするための完全な StreamingController.UploadDatabase メソッド:The complete StreamingController.UploadDatabase method for streaming to a database with EF Core:

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
    {
        ModelState.AddModelError("File", 
            $"The request couldn't be processed (Error 1).");
        // Log error

        return BadRequest(ModelState);
    }

    // Accumulate the form data key-value pairs in the request (formAccumulator).
    var formAccumulator = new KeyValueAccumulator();
    var trustedFileNameForDisplay = string.Empty;
    var untrustedFileNameForStorage = string.Empty;
    var streamedFileContent = new byte[0];

    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        _defaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, HttpContext.Request.Body);

    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    {
        var hasContentDispositionHeader = 
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out var contentDisposition);

        if (hasContentDispositionHeader)
        {
            if (MultipartRequestHelper
                .HasFileContentDisposition(contentDisposition))
            {
                untrustedFileNameForStorage = contentDisposition.FileName.Value;
                // Don't trust the file name sent by the client. To display
                // the file name, HTML-encode the value.
                trustedFileNameForDisplay = WebUtility.HtmlEncode(
                        contentDisposition.FileName.Value);

                streamedFileContent = 
                    await FileHelpers.ProcessStreamedFile(section, contentDisposition, 
                        ModelState, _permittedExtensions, _fileSizeLimit);

                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }
            }
            else if (MultipartRequestHelper
                .HasFormDataContentDisposition(contentDisposition))
            {
                // Don't limit the key name length because the 
                // multipart headers length limit is already in effect.
                var key = HeaderUtilities
                    .RemoveQuotes(contentDisposition.Name).Value;
                var encoding = GetEncoding(section);

                if (encoding == null)
                {
                    ModelState.AddModelError("File", 
                        $"The request couldn't be processed (Error 2).");
                    // Log error

                    return BadRequest(ModelState);
                }

                using (var streamReader = new StreamReader(
                    section.Body,
                    encoding,
                    detectEncodingFromByteOrderMarks: true,
                    bufferSize: 1024,
                    leaveOpen: true))
                {
                    // The value length limit is enforced by 
                    // MultipartBodyLengthLimit
                    var value = await streamReader.ReadToEndAsync();

                    if (string.Equals(value, "undefined", 
                        StringComparison.OrdinalIgnoreCase))
                    {
                        value = string.Empty;
                    }

                    formAccumulator.Append(key, value);

                    if (formAccumulator.ValueCount > 
                        _defaultFormOptions.ValueCountLimit)
                    {
                        // Form key count limit of 
                        // _defaultFormOptions.ValueCountLimit 
                        // is exceeded.
                        ModelState.AddModelError("File", 
                            $"The request couldn't be processed (Error 3).");
                        // Log error

                        return BadRequest(ModelState);
                    }
                }
            }
        }

        // Drain any remaining section body that hasn't been consumed and
        // read the headers for the next section.
        section = await reader.ReadNextSectionAsync();
    }

    // Bind form data to the model
    var formData = new FormData();
    var formValueProvider = new FormValueProvider(
        BindingSource.Form,
        new FormCollection(formAccumulator.GetResults()),
        CultureInfo.CurrentCulture);
    var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
        valueProvider: formValueProvider);

    if (!bindingSuccessful)
    {
        ModelState.AddModelError("File", 
            "The request couldn't be processed (Error 5).");
        // Log error

        return BadRequest(ModelState);
    }

    // **WARNING!**
    // In the following example, the file is saved without
    // scanning the file's contents. In most production
    // scenarios, an anti-virus/anti-malware scanner API
    // is used on the file before making the file available
    // for download or for use by other systems. 
    // For more information, see the topic that accompanies 
    // this sample app.

    var file = new AppFile()
    {
        Content = streamedFileContent,
        UntrustedName = untrustedFileNameForStorage,
        Note = formData.Note,
        Size = streamedFileContent.Length, 
        UploadDT = DateTime.UtcNow
    };

    _context.File.Add(file);
    await _context.SaveChangesAsync();

    return Created(nameof(StreamingController), null);
}

MultipartRequestHelper (Utilities/MultipartRequestHelper.cs):MultipartRequestHelper (Utilities/MultipartRequestHelper.cs):

using System;
using System.IO;
using Microsoft.Net.Http.Headers;

namespace SampleApp.Utilities
{
    public static class MultipartRequestHelper
    {
        // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
        // The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
        public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
        {
            var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;

            if (string.IsNullOrWhiteSpace(boundary))
            {
                throw new InvalidDataException("Missing content-type boundary.");
            }

            if (boundary.Length > lengthLimit)
            {
                throw new InvalidDataException(
                    $"Multipart boundary length limit {lengthLimit} exceeded.");
            }

            return boundary;
        }

        public static bool IsMultipartContentType(string contentType)
        {
            return !string.IsNullOrEmpty(contentType)
                   && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
        }

        public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
        {
            // Content-Disposition: form-data; name="key";
            return contentDisposition != null
                && contentDisposition.DispositionType.Equals("form-data")
                && string.IsNullOrEmpty(contentDisposition.FileName.Value)
                && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
        }

        public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
        {
            // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
            return contentDisposition != null
                && contentDisposition.DispositionType.Equals("form-data")
                && (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
                    || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
        }
    }
}

物理的な場所にストリーミングするための完全な StreamingController.UploadPhysical メソッド:The complete StreamingController.UploadPhysical method for streaming to a physical location:

[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
    {
        ModelState.AddModelError("File", 
            $"The request couldn't be processed (Error 1).");
        // Log error

        return BadRequest(ModelState);
    }

    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        _defaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, HttpContext.Request.Body);
    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    {
        var hasContentDispositionHeader = 
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out var contentDisposition);

        if (hasContentDispositionHeader)
        {
            // This check assumes that there's a file
            // present without form data. If form data
            // is present, this method immediately fails
            // and returns the model error.
            if (!MultipartRequestHelper
                .HasFileContentDisposition(contentDisposition))
            {
                ModelState.AddModelError("File", 
                    $"The request couldn't be processed (Error 2).");
                // Log error

                return BadRequest(ModelState);
            }
            else
            {
                // Don't trust the file name sent by the client. To display
                // the file name, HTML-encode the value.
                var trustedFileNameForDisplay = WebUtility.HtmlEncode(
                        contentDisposition.FileName.Value);
                var trustedFileNameForFileStorage = Path.GetRandomFileName();

                // **WARNING!**
                // In the following example, the file is saved without
                // scanning the file's contents. In most production
                // scenarios, an anti-virus/anti-malware scanner API
                // is used on the file before making the file available
                // for download or for use by other systems. 
                // For more information, see the topic that accompanies 
                // this sample.

                var streamedFileContent = await FileHelpers.ProcessStreamedFile(
                    section, contentDisposition, ModelState, 
                    _permittedExtensions, _fileSizeLimit);

                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }

                using (var targetStream = System.IO.File.Create(
                    Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
                {
                    await targetStream.WriteAsync(streamedFileContent);

                    _logger.LogInformation(
                        "Uploaded file '{TrustedFileNameForDisplay}' saved to " +
                        "'{TargetFilePath}' as {TrustedFileNameForFileStorage}", 
                        trustedFileNameForDisplay, _targetFilePath, 
                        trustedFileNameForFileStorage);
                }
            }
        }

        // Drain any remaining section body that hasn't been consumed and
        // read the headers for the next section.
        section = await reader.ReadNextSectionAsync();
    }

    return Created(nameof(StreamingController), null);
}

サンプル アプリでは、検証チェックは FileHelpers.ProcessStreamedFile によって処理されます。In the sample app, validation checks are handled by FileHelpers.ProcessStreamedFile.

検証Validation

サンプル アプリの FileHelpers クラスでは、バッファーリングされた IFormFile とストリーミングされたファイルのアップロードに関するいくつかのチェックが示されています。The sample app's FileHelpers class demonstrates a several checks for buffered IFormFile and streamed file uploads. サンプル アプリでのバッファーリングされたファイルのアップロード IFormFile の処理については、Utilities/FileHelpers.cs ファイルの ProcessFormFile メソッドを参照してください。For processing IFormFile buffered file uploads in the sample app, see the ProcessFormFile method in the Utilities/FileHelpers.cs file. ストリーミングされたファイルの処理については、同じファイルの ProcessStreamedFile メソッドを参照してください。For processing streamed files, see the ProcessStreamedFile method in the same file.

警告

サンプル アプリで示されている検証処理メソッドでは、アップロードされたファイルの内容はスキャンされません。The validation processing methods demonstrated in the sample app don't scan the content of uploaded files. ほとんどの運用シナリオでは、ファイルをユーザーまたは他のシステムで使用できるようにする前に、ウイルス/マルウェア スキャナー API が使用されます。In most production scenarios, a virus/malware scanner API is used on the file before making the file available to users or other systems.

このトピックのサンプルでは検証技法の実際の例が示されていますが、次の場合を除き、運用アプリでは FileHelpers クラスを実装しないでください。Although the topic sample provides a working example of validation techniques, don't implement the FileHelpers class in a production app unless you:

  • 実装を完全に理解している。Fully understand the implementation.
  • アプリの環境と仕様に合わせて実装が適切に変更されている。Modify the implementation as appropriate for the app's environment and specifications.

これらの要件に対応することなく、アプリにセキュリティ コードをむやみに実装しないでください。Never indiscriminately implement security code in an app without addressing these requirements.

コンテンツの検証Content validation

アップロードされた内容に対してサードパーティ製のウイルス/マルウェア スキャン API を使用します。Use a third party virus/malware scanning API on uploaded content.

大容量のシナリオでは、ファイルのスキャンに大量のサーバー リソースが必要です。Scanning files is demanding on server resources in high volume scenarios. ファイルのスキャンによって要求の処理パフォーマンスが低下する場合は、スキャン処理をバックグラウンド サービスにオフロードすることを検討してください (たとえば、アプリのサーバーとは異なるサーバーで実行されているサービス)。If request processing performance is diminished due to file scanning, consider offloading the scanning work to a background service, possibly a service running on a server different from the app's server. 通常、アップロードされたファイルは、バックグラウンドのウイルス検索プログラムによってチェックされるまで、検疫された領域に保持されます。Typically, uploaded files are held in a quarantined area until the background virus scanner checks them. 合格したファイルは、通常のファイル ストレージの場所に移動されます。When a file passes, the file is moved to the normal file storage location. これらの手順は、通常、ファイルのスキャン状態を示すデータベース レコードと共に実行されます。These steps are usually performed in conjunction with a database record that indicates the scanning status of a file. このような方法を使用することで、アプリとアプリ サーバーは要求への応答に集中できます。By using such an approach, the app and app server remain focused on responding to requests.

ファイル拡張子の検証File extension validation

アップロードされたファイルの拡張子を、許可されている拡張子のリストで確認する必要があります。The uploaded file's extension should be checked against a list of permitted extensions. 次に例を示します。For example:

private string[] permittedExtensions = { ".txt", ".pdf" };

var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();

if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
    // The extension is invalid ... discontinue processing the file
}

ファイルのシグネチャの検証File signature validation

ファイルのシグネチャは、ファイルの先頭にある最初の数バイトによって決定されます。A file's signature is determined by the first few bytes at the start of a file. これらのバイトを使用して、拡張子がファイルの内容と一致するかどうかを示すことができます。These bytes can be used to indicate if the extension matches the content of the file. サンプル アプリでは、いくつかの一般的なファイルの種類についてファイルのシグネチャがチェックされています。The sample app checks file signatures for a few common file types. 次の例では、JPEG イメージのファイルのシグネチャが、ファイルに対してチェックされています。In the following example, the file signature for a JPEG image is checked against the file:

private static readonly Dictionary<string, List<byte[]>> _fileSignature = 
    new Dictionary<string, List<byte[]>>
{
    { ".jpeg", new List<byte[]>
        {
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
        }
    },
};

using (var reader = new BinaryReader(uploadedFileData))
{
    var signatures = _fileSignature[ext];
    var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));

    return signatures.Any(signature => 
        headerBytes.Take(signature.Length).SequenceEqual(signature));
}

追加のファイル シグネチャを取得するには、「ファイル シグネチャ データベース」および公式なファイルの仕様を参照してください。To obtain additional file signatures, see the File Signatures Database and official file specifications.

ファイル名のセキュリティFile name security

ファイルを物理ストレージに保存する場合は、クライアント指定のファイル名を使用しないでください。Never use a client-supplied file name for saving a file to physical storage. Path.GetRandomFileName または Path.GetTempFileName を使用して、ファイルの安全なファイル名を作成し、一時ストレージの完全なパス (ファイル名を含む) を作成します。Create a safe file name for the file using Path.GetRandomFileName or Path.GetTempFileName to create a full path (including the file name) for temporary storage.

Razor では、プロパティ値が表示用に自動的に HTML エンコードされます。Razor automatically HTML encodes property values for display. 次のコードは安全に使用できます。The following code is safe to use:

@foreach (var file in Model.DatabaseFiles) {
    <tr>
        <td>
            @file.UntrustedName
        </td>
    </tr>
}

Razor の外部では、ユーザーの要求からのファイル名の内容を常に HtmlEncode でエンコードします。Outside of Razor, always HtmlEncode file name content from a user's request.

多くの実装には、ファイルが存在するかどうかのチェックを含める必要があります。そうしないと、同じ名前のファイルによってファイルが上書きされます。Many implementations must include a check that the file exists; otherwise, the file is overwritten by a file of the same name. アプリの仕様を満たす追加のロジックを提供します。Supply additional logic to meet your app's specifications.

サイズの検証Size validation

アップロードされるファイルのサイズを制限します。Limit the size of uploaded files.

サンプル アプリでは、ファイルのサイズは 2 MB (バイト単位) に制限されています。In the sample app, the size of the file is limited to 2 MB (indicated in bytes). その制限は、appsettings.json ファイルの Configuration によって提供されます。The limit is supplied via Configuration from the appsettings.json file:

{
  "FileSizeLimit": 2097152
}

FileSizeLimitPageModel クラスに挿入されます。The FileSizeLimit is injected into PageModel classes:

public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    private readonly long _fileSizeLimit;

    public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
    {
        _fileSizeLimit = config.GetValue<long>("FileSizeLimit");
    }

    ...
}

ファイルのサイズが制限を超えると、ファイルは拒否されます。When a file size exceeds the limit, the file is rejected:

if (formFile.Length > _fileSizeLimit)
{
    // The file is too large ... discontinue processing the file
}

name 属性の値を POST メソッドのパラメーター名に一致させるMatch name attribute value to parameter name of POST method

フォーム データを POST する、または JavaScript の FormData を直接使用する、Razor 以外のフォームでは、フォームの要素または FormData で指定されている名前が、コントローラーのアクションのパラメーターの name と一致している必要があります。In non-Razor forms that POST form data or use JavaScript's FormData directly, the name specified in the form's element or FormData must match the name of the parameter in the controller's action.

次に例を示します。In the following example:

  • <input> 要素を使用すると、name 属性には値 battlePlans が設定されます。When using an <input> element, the name attribute is set to the value battlePlans:

    <input type="file" name="battlePlans" multiple>
    
  • JavaScript で FormData を使用すると、name には値 battlePlans が設定されます。When using FormData in JavaScript, the name is set to the value battlePlans:

    var formData = new FormData();
    
    for (var file in files) {
      formData.append("battlePlans", file, file.name);
    }
    

C# メソッドのパラメーターに一致する名前を使用します (battlePlans)。Use a matching name for the parameter of the C# method (battlePlans):

  • Upload という名前の Razor Pages ページ ハンドラー メソッドの場合:For a Razor Pages page handler method named Upload:

    public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
    
  • MVC POST コントローラー アクション メソッドの場合:For an MVC POST controller action method:

    public async Task<IActionResult> Post(List<IFormFile> battlePlans)
    

サーバーとアプリの構成Server and app configuration

マルチパート本文の長さの制限Multipart body length limit

MultipartBodyLengthLimit では、各マルチパート本文の長さの制限が設定されます。MultipartBodyLengthLimit sets the limit for the length of each multipart body. この制限を超えるフォーム セクションでは、解析時に InvalidDataException がスローされます。Form sections that exceed this limit throw an InvalidDataException when parsed. 既定値は 134,217,728 (128 MB) です。The default is 134,217,728 (128 MB). 制限をカスタマイズするには、Startup.ConfigureServices の設定 MultipartBodyLengthLimit を使用します。Customize the limit using the MultipartBodyLengthLimit setting in Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<FormOptions>(options =>
    {
        // Set the limit to 256 MB
        options.MultipartBodyLengthLimit = 268435456;
    });
}

単一ページまたはアクションに対する MultipartBodyLengthLimit を設定するには、RequestFormLimitsAttribute を使用します。RequestFormLimitsAttribute is used to set the MultipartBodyLengthLimit for a single page or action.

Razor Pages アプリでは、Startup.ConfigureServicesconvention を使用してフィルターを適用します。In a Razor Pages app, apply the filter with a convention in Startup.ConfigureServices:

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        options.Conventions
            .AddPageApplicationModelConvention("/FileUploadPage",
                model.Filters.Add(
                    new RequestFormLimitsAttribute()
                    {
                        // Set the limit to 256 MB
                        MultipartBodyLengthLimit = 268435456
                    });
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Razor Pages アプリまたは MVC アプリでは、ページ モデルまたはアクション メソッドにフィルターを適用します。In a Razor Pages app or an MVC app, apply the filter to the page model or action method:

// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    ...
}

Kestrel の最大要求本文サイズKestrel maximum request body size

Kestrel によってホストされるアプリの場合、既定の要求本文の最大サイズは、30,000,000 バイトです。これは約 28.6 MB になります。For apps hosted by Kestrel, the default maximum request body size is 30,000,000 bytes, which is approximately 28.6 MB. 制限をカスタマイズするには、MaxRequestBodySize Kestrel サーバー オプションを使用します。Customize the limit using the MaxRequestBodySize Kestrel server option:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureKestrel((context, options) =>
        {
            // Handle requests up to 50 MB
            options.Limits.MaxRequestBodySize = 52428800;
        });

単一ページまたはアクションに対する MaxRequestBodySize を設定するには、RequestSizeLimitAttribute を使用します。RequestSizeLimitAttribute is used to set the MaxRequestBodySize for a single page or action.

Razor Pages アプリでは、Startup.ConfigureServicesconvention を使用してフィルターを適用します。In a Razor Pages app, apply the filter with a convention in Startup.ConfigureServices:

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        options.Conventions
            .AddPageApplicationModelConvention("/FileUploadPage",
                model =>
                {
                    // Handle requests up to 50 MB
                    model.Filters.Add(
                        new RequestSizeLimitAttribute(52428800));
                });
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Razor Pages アプリまたは MVC アプリでは、ページ ハンドラー クラスまたはアクション メソッドにフィルターを適用します。In a Razor pages app or an MVC app, apply the filter to the page handler class or action method:

// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
    ...
}

その他の Kestrel の制限Other Kestrel limits

Kestrel によってホストされるアプリには、他の Kestrel の制限が適用される場合があります。Other Kestrel limits may apply for apps hosted by Kestrel:

IIS の内容の長さの制限IIS content length limit

既定の要求の制限 (maxAllowedContentLength) は、30,000,000 バイトです。これは約 28.6 MB になります。The default request limit (maxAllowedContentLength) is 30,000,000 bytes, which is approximately 28.6MB. web.config ファイルで制限をカスタマイズします。Customize the limit in the web.config file:

<system.webServer>
  <security>
    <requestFiltering>
      <!-- Handle requests up to 50 MB -->
      <requestLimits maxAllowedContentLength="52428800" />
    </requestFiltering>
  </security>
</system.webServer>

この設定は IIS にのみ適用されます。This setting only applies to IIS. Kestrel でホストする場合、既定ではこの動作は発生しません。The behavior doesn't occur by default when hosting on Kestrel. 詳細については、「要求制限 <requestLimits>」を参照してください。For more information, see Request Limits <requestLimits>.

ASP.NET Core モジュールでの制限または IIS 要求フィルター モジュールの存在により、アップロードが 2 または 4 GB に制限される場合があります。Limitations in the ASP.NET Core Module or presence of the IIS Request Filtering Module may limit uploads to either 2 or 4 GB. 詳細については、「サイズが 2 GB を超えるファイルをアップロードできない (dotnet/AspNetCore #2711)」を参照してください。For more information, see Unable to upload file greater than 2GB in size (dotnet/AspNetCore #2711).

トラブルシューティングTroubleshoot

ファイルのアップロード時に発生する一般的ないくつかの問題と、考えられる解決策を以下に示します。Below are some common problems encountered when working with uploading files and their possible solutions.

IIS サーバーに展開したときの "見つかりません" エラーNot Found error when deployed to an IIS server

次のエラーは、アップロードされるファイルがサーバーの構成されている内容の長さを超えていることを示します。The following error indicates that the uploaded file exceeds the server's configured content length:

HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.

制限を増やす方法の詳細については、「IIS の内容の長さの制限」セクションを参照してください。For more information on increasing the limit, see the IIS content length limit section.

接続エラーConnection failure

接続エラーおよびサーバー接続のリセットは、アップロードされるファイルが Kestrel の最大要求本文サイズを超えていることを示している場合があります。A connection error and a reset server connection probably indicates that the uploaded file exceeds Kestrel's maximum request body size. 詳細については、Kestrel の最大要求本文サイズ」セクションを参照してください。For more information, see the Kestrel maximum request body size section. Kestrel クライアント接続の制限の調整も必要な場合があります。Kestrel client connection limits may also require adjustment.

IFormFile での null 参照例外Null Reference Exception with IFormFile

コントローラーが IFormFile を使用してアップロードされたファイルを受け取っても、値が null の場合は、HTML フォームで multipart/form-data に値 enctype が指定されていることを確認します。If the controller is accepting uploaded files using IFormFile but the value is null, confirm that the HTML form is specifying an enctype value of multipart/form-data. この属性が <form> 要素で設定されていない場合、ファイルはアップロードされず、バインドされた IFormFile 引数は null になります。If this attribute isn't set on the <form> element, the file upload doesn't occur and any bound IFormFile arguments are null. また、フォーム データでのアップロードの名前がアプリの名前と一致することを確認します。Also confirm that the upload naming in form data matches the app's naming.

ストリームが長すぎるStream was too long

このトピックの例は、アップロードされたファイル コンテンツを保持するために MemoryStream に依存しています。The examples in this topic rely upon MemoryStream to hold the uploaded file's content. int.MaxValue のサイズ制限は MemoryStream です。The size limit of a MemoryStream is int.MaxValue. アプリのファイル アップロード シナリオで 50 MB を超えるファイル コンテンツを保持する必要がある場合は、アップロードされたファイルのコンテンツを 1 つの MemoryStream に依存することなく保持する別のアプローチを使用します。If the app's file upload scenario requires holding file content larger than 50 MB, use an alternative approach that doesn't rely upon a single MemoryStream for holding an uploaded file's content.

その他の技術情報Additional resources