netcore 文件服务器,在 ASP.NET Core 中上传文件

2023-11-19

ASP.NET Core 支持使用缓冲的模型绑定(针对较小文件)和无缓冲的流式传输(针对较大文件)上传一个或多个文件。

安全注意事项

向用户提供向服务器上传文件的功能时,必须格外小心。 攻击者可能会尝试执行以下操作:

执行拒绝服务攻击。

上传病毒或恶意软件。

以其他方式破坏网络和服务器。

降低成功攻击可能性的安全措施如下:

将文件上传到专用文件上传区域,最好是非系统驱动器。 使用专用位置便于对上传的文件实施安全限制。 禁用对文件上传位置的执行权限。†

请勿将上传的文件保存在与应用相同的目录树中。†

使用应用确定的安全的文件名。 请勿使用用户提供的文件名或上载文件的不受信任的文件名。 † 显示时,HTML 对不受信任的文件名进行编码。 例如,记录文件名或在 UI 中显示 (Razor 会自动对输出) 进行 HTML 编码。

仅允许应用设计规范的已批准文件扩展名。†

验证是否在服务器上执行了客户端检查。 † 客户端检查很容易规避。

检查已上传文件的大小。 设置大小上限以防止上传大型文件。†

文件不应该被具有相同名称的上传文件覆盖时,先在数据库或物理存储上检查文件名,然后再上传文件。

先对上传的内容运行病毒/恶意软件扫描程序,然后再存储文件。

†示例应用演示了符合条件的方法。

警告

将恶意代码上传到系统通常是执行代码的第一步,这些代码可以:

完全获得对系统的控制权限。

重载系统,导致系统崩溃。

泄露用户或系统数据。

将涂鸦应用于公共 UI。

有关在接受用户文件时减少攻击外围应用的信息,请参阅以下资源:

有关实现安全措施(包括示例应用中的示例)的详细信息,请参阅验证部分。

存储方案

常见的文件存储选项有:

数据库

对于小型文件上传,数据库通常快于物理存储(文件系统或网络共享)选项。

相对于物理存储选项,数据库通常更为便利,因为检索数据库记录来获取用户数据可同时提供文件内容(如头像图像)。

相对于使用数据存储服务,数据库的成本可能更低。

物理存储(文件系统或网络共享)

对于大型文件上传:

数据库限制可能会限制上传的大小。

相对于数据库存储,物理存储通常成本更高。

相对于使用数据存储服务,物理存储的成本可能更低。

应用的进程必须具有存储位置的读写权限。 切勿授予执行权限。

服务通常通过本地解决方案提供提升的可伸缩性和复原能力,而它们往往受单一故障点的影响。

在大型存储基础结构方案中,服务的成本可能更低。

文件上传方案

缓冲和流式传输是上传文件的两种常见方法。

缓冲

整个文件读入 IFormFile,它是文件的 C# 表示形式,用于处理或保存文件。

文件上传所用的资源(磁盘、内存)取决于并发文件上传的数量和大小。 如果应用尝试缓冲过多上传,站点就会在内存或磁盘空间不足时崩溃。 如果文件上传的大小或频率会消耗应用资源,请使用流式传输。

备注

会将大于 64 KB 的所有单个缓冲文件从内存移到磁盘的临时文件。

本主题的以下部分介绍了如何缓冲小型文件:

流式处理

从多部分请求收到文件,然后应用直接处理或保存它。 流式传输无法显著提高性能。 流式传输可降低上传文件时对内存或磁盘空间的需求。

通过流式传输上传大型文件部分介绍了如何流式传输大型文件。

通过缓冲的模型绑定将小型文件上传到物理存储

要上传小文件,请使用多部分窗体或使用 JavaScript 构造 POST 请求。

下面的示例演示 Razor 如何使用页面窗体上传示例应用) 中的单个文件 (Pages/BufferedSingleFileUploadPhysical :

下面的示例与前面的示例类似,不同之处在于:

使用 JavaScript (Fetch API) 提交窗体的数据。

无验证。

enctype="multipart/form-data" οnsubmit="AJAXSubmit(this);return false;"

method="post">

File

name="FileUpload.FormFile" />

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);

}

}

若要使用 JavaScript 为不支持 Fetch API 的客户端执行窗体发布,请使用以下方法之一:

使用 XMLHttpRequest。 例如:

"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));

}

为支持文件上传,HTML 窗体必须指定 multipart/form-data 的编码类型 (enctype)。

要使 files 输入元素支持上传多个文件,请在 元素上提供 multiple 属性:

上传到服务器的单个文件可使用 IFormFile 接口通过模型绑定进行访问。 示例应用演示了数据库和物理存储方案的多个缓冲文件上传。

警告

除了显示和日志记录用途外,请勿使用 IFormFile 的 FileName 属性。 显示或日志记录时,HTML 对文件名进行编码。 攻击者可以提供恶意文件名,包括完整路径或相对路径。 应用程序应:

从用户提供的文件名中删除路径。

为 UI 或日志记录保存经 HTML 编码、已删除路径的文件名。

生成新的随机文件名进行存储。

以下代码可从文件名中删除路径:

string untrustedFileName = Path.GetFileName(pathName);

目前提供的示例未考虑安全注意事项。 以下各节及示例应用提供了其他信息:

使用模型绑定和 IFormFile 上传文件时,操作方法可以接受以下内容:

备注

绑定根据名称匹配窗体文件。 例如, 中的 HTML name 值必须与 C# 参数/属性绑定 (FormFile) 匹配。 有关详细信息,请参阅使名称属性值与 POST 方法的参数名匹配部分。

如下示例中:

循环访问一个或多个上传的文件。

使用应用生成的文件名将文件保存到本地文件系统。

返回上传的文件的总数量和总大小。

public async Task OnPostUploadAsync(List 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 });

}

使用 Path.GetRandomFileName 生成文件名(不含路径)。 在下面的示例中,从配置获取路径:

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);

}

}

}

使用 IFormFile 技术上传的文件在处理之前会缓冲在内存中或服务器的磁盘中。 在操作方法中,IFormFile 内容可作为 Stream 访问。 除本地文件系统之外,还可以将文件保存到网络共享或文件存储服务,如 Azure Blob 存储。

若要查看循环访问要上传的多个文件并且使用安全文件名的其他示例,请参阅示例应用中的 Pages/BufferedMultipleFileUploadPhysical.cshtml.cs。

警告

如果在未删除先前临时文件的情况下创建了 65,535 个以上的文件,则 Path.GetTempFileName 将抛出一个 IOException。 65,535 个文件限制是每个服务器的限制。 有关 Windows 操作系统上的此限制的详细信息,请参阅以下主题中的说明:

使用缓冲的模型绑定将小型文件上传到数据库

要使用实体框架将二进制文件数据存储在数据库中,请在实体上定义 Byte 数组属性:

public class AppFile

{

public int Id { get; set; }

public byte[] Content { get; set; }

}

为包括 IFormFile 的类指定页模型属性:

public class BufferedSingleFileUploadDbModel : PageModel

{

...

[BindProperty]

public BufferedSingleFileUploadDb FileUpload { get; set; }

...

}

public class BufferedSingleFileUploadDb

{

[Required]

[Display(Name="File")]

public IFormFile FormFile { get; set; }

}

备注

IFormFile 可以直接用作操作方法参数或绑定模型属性。 前面的示例使用绑定模型属性。

FileUpload在 Razor 页面窗体中使用:

将窗体发布到服务器后,将 IFormFile 复制到流,并将它作为字节数组保存在数据库中。 在下面的示例中,_dbContext 存储应用的数据库上下文:

public async Task 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();

}

上面的示例与示例应用中演示的方案相似:

Pages/BufferedSingleFileUploadDb.cshtml

Pages/BufferedSingleFileUploadDb.cshtml.cs

警告

在关系数据库中存储二进制数据时要格外小心,因为它可能对性能产生不利影响。

切勿依赖或信任未经验证的 IFormFile 的 FileName 属性。 只应将 FileName 属性用于显示用途,并且只应在进行 HTML 编码后使用它。

提供的示例未考虑安全注意事项。 以下各节及示例应用提供了其他信息:

通过流式传输上传大型文件

以下示例演示如何使用 JavaScript 将文件流式传输到控制器操作。 使用自定义筛选器属性生成文件的防伪令牌,并将其传递到客户端 HTTP 头中(而不是在请求正文中传递)。 由于操作方法直接处理上传的数据,所以其他自定义筛选器会禁用窗体模型绑定。 在该操作中,使用 MultipartReader 读取窗体的内容,它会读取每个单独的 MultipartSection,从而根据需要处理文件或存储内容。 读取多部分节后,该操作会执行自己的模型绑定。

初始页面响应会加载窗体,并 cookie 通过属性) 将防伪标记保存在 (中 GenerateAntiforgeryTokenCookieAttribute 。 属性使用 ASP.NET Core 的内置 防伪支持 来设置 cookie 具有请求令牌的:

public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute

{

public override void OnResultExecuting(ResultExecutingContext context)

{

var antiforgery = context.HttpContext.RequestServices.GetService();

// 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 禁用模型绑定:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]

public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter

{

public void OnResourceExecuting(ResourceExecutingContext context)

{

var factories = context.ValueProviderFactories;

factories.RemoveType();

factories.RemoveType();

}

public void OnResourceExecuted(ResourceExecutedContext context)

{

}

}

在示例应用中, GenerateAntiforgeryTokenCookieAttribute 和 DisableFormValueModelBindingAttribute /StreamedSingleFileUploadDb /StreamedSingleFileUploadPhysical Startup.ConfigureServices 使用Razor 页面约定作为筛选器应用于和的页面应用程序模型:

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);

由于模型绑定不读取窗体,因此不绑定从窗体绑定的参数(查询、路由和标头继续运行)。 操作方法直接使用 Request 属性。 MultipartReader 用于读取每个节。 在 KeyValueAccumulator 中存储键值数据。 读取多部分节后,系统会使用 KeyValueAccumulator 的内容将窗体数据绑定到模型类型。

使用 EF Core 流式传输到数据库的完整 StreamingController.UploadDatabase 方法:

[HttpPost]

[DisableFormValueModelBinding]

[ValidateAntiForgeryToken]

public async Task 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 = Array.Empty();

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):

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 方法:

[HttpPost]

[DisableFormValueModelBinding]

[ValidateAntiForgeryToken]

public async Task 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 处理验证检查。

验证

示例应用的 FileHelpers 类演示对缓冲 IFormFile 和流式传输文件上传的多项检查。 有关示例应用如何处理 IFormFile 缓冲文件上传的信息,请参阅 Utilities/FileHelpers.cs 文件中的 ProcessFormFile 方法。 有关如何处理流式传输的文件的信息,请参阅同一个文件中的 ProcessStreamedFile 方法。

警告

示例应用演示的验证处理方法不扫描上传的文件的内容。 在多数生产方案中,会先将病毒/恶意软件扫描程序 API 用于文件,然后再向用户或其他系统提供文件。

尽管主题示例提供了验证技巧工作示例,但是如果不满足以下情况,请勿在生产应用中实现 FileHelpers 类:

完全理解此实现。

根据应用的环境和规范修改实现。

切勿未处理这些要求即随意在应用中实现安全代码。

内容验证

将第三方病毒/恶意软件扫描 API 用于上传的内容。

在大容量方案中,在服务器资源上扫描文件较为困难。 若文件扫描导致请求处理性能降低,请考虑将扫描工作卸载到后台服务,该服务可以是在应用服务器之外的服务器上运行的服务。 通常会将卸载的文件保留在隔离区,直至后台病毒扫描程序检查它们。 文件通过检查时,会将相应的文件移到常规的文件存储位置。 通常在执行这些步骤的同时,会提供指示文件扫描状态的数据库记录。 通过此方法,应用和应用服务器可以持续以响应请求为重点。

文件扩展名验证

应在允许的扩展名列表中查找上传的文件的扩展名。 例如:

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

}

文件签名验证

文件的签名由文件开头部分中的前几个字节确定。 可以使用这些字节指示扩展名是否与文件内容匹配。 示例应用检查一些常见文件类型的文件签名。 在下面的示例中,在文件上检查 JPEG 图像的文件签名:

private static readonly Dictionary> _fileSignature =

new Dictionary>

{

{ ".jpeg", new List

{

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));

}

若要获取其他文件签名,请参阅文件签名数据库和官方文件规范。

文件名安全

切勿使用客户端提供的文件名来将文件保存到物理存储。 使用 Path.GetRandomFileName 或 Path.GetTempFileName 为文件创建安全的文件名,以创建完整路径(包括文件名)来执行临时存储。

Razor 自动对属性值进行 HTML 编码以便显示。 以下代码安全可用:

@foreach (var file in Model.DatabaseFiles) {

@file.UntrustedName

}

在 Razor 之外 HtmlEncode ,始终来自用户请求的文件名内容。

许多实现都必须包含关于文件是否存在的检查;否则文件会被使用相同名称的文件覆盖。 提供其他逻辑以符合应用的规范。

大小验证

限制上传的文件的大小。

在示例应用中,文件大小限制为 2 MB(以字节为单位)。 限制通过 文件的配置 appsettings.json 提供:

{

"FileSizeLimit": 2097152

}

将 FileSizeLimit 注入到 PageModel 类:

public class BufferedSingleFileUploadPhysicalModel : PageModel

{

private readonly long _fileSizeLimit;

public BufferedSingleFileUploadPhysicalModel(IConfiguration config)

{

_fileSizeLimit = config.GetValue("FileSizeLimit");

}

...

}

文件大小超出限制时,将拒绝文件:

if (formFile.Length > _fileSizeLimit)

{

// The file is too large ... discontinue processing the file

}

使名称属性值与 POST 方法的参数名称匹配

在 POST 形成数据或直接使用 JavaScript 的非窗体中,窗体的 元素中指定的名称或必须与控制器操作中的参数名称 Razor FormData FormData 匹配。

如下示例中:

使用 元素时,将 name 属性设置为值 battlePlans:

使用 JavaScript FormData 时,将名称设置为值 battlePlans:

var formData = new FormData();

for (var file in files) {

formData.append("battlePlans", file, file.name);

}

将匹配的名称用于 C# 方法的参数 (battlePlans):

对于名为 Razor 的 Pages 页处理程序方法 Upload :

public async Task OnPostUploadAsync(List battlePlans)

对于 MVC POST 控制器操作方法:

public async Task Post(List battlePlans)

服务器和应用程序配置

多部分正文长度限制

MultipartBodyLengthLimit 设置每个多部分正文的长度限制。 分析超出此限制的窗体部分时,会引发 InvalidDataException。 默认值为 134,217,728 (128 MB)。 使用 Startup.ConfigureServices 中的 MultipartBodyLengthLimit 设置自定义此限制:

public void ConfigureServices(IServiceCollection services)

{

services.Configure(options =>

{

// Set the limit to 256 MB

options.MultipartBodyLengthLimit = 268435456;

});

}

在 Razor Pages 应用中,使用 中的约定 应用 筛选器 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 应用中,将筛选器应用于页面模型或操作方法:

// Set the limit to 256 MB

[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]

public class BufferedSingleFileUploadPhysicalModel : PageModel

{

...

}

Kestrel 最大请求正文大小

对于 承载的应用 Kestrel ,默认的最大请求正文大小为 30,000,000 字节,大约为 28.6 MB。 使用 MaxRequestBodySize 服务器选项 Kestrel 自定义限制:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>

WebHost.CreateDefaultBuilder(args)

.UseStartup()

.ConfigureKestrel((context, options) =>

{

// Handle requests up to 50 MB

options.Limits.MaxRequestBodySize = 52428800;

});

在 Razor Pages 应用中,使用 中的约定 应用 筛选器 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 页面应用或 MVC 应用中,将筛选器应用于页面处理程序类或操作方法:

// Handle requests up to 50 MB

[RequestSizeLimit(52428800)]

public class BufferedSingleFileUploadPhysicalModel : PageModel

{

...

}

其他 Kestrel 限制

其他 Kestrel 限制可能适用于 托管的应用 Kestrel :

IIS

默认请求限制 () maxAllowedContentLength 30,000,000 字节,大约为 28.6 MB。 自定义文件中 web.config 的限制。 在下面的示例中,限制设置为 50 MB, (52,428,800 字节) :

maxAllowedContentLength该设置仅适用于 IIS。 有关详细信息,请参阅请求限制 。

在 中设置 ,增加 HTTP 请求的最大请求正文 IISServerOptions.MaxRequestBodySize 大小 Startup.ConfigureServices 。 在下面的示例中,限制设置为 50 MB, (52,428,800 字节) :

services.Configure(options =>

{

options.MaxRequestBodySize = 52428800;

});

疑难解答

以下是上传文件时遇到的一些常见问题及其可能的解决方案。

部署到 IIS 服务器时出现“找不到”错误

以下错误表示上传的文件超过服务器配置的内容长度:

HTTP 404.13 - Not Found

The request filtering module is configured to deny a request that exceeds the request content length.

有关详细信息,请参阅 IIS 部分。

连接失败

连接错误和重置服务器连接可能表示上传的文件超过了 Kestrel 最大请求正文大小。 有关详细信息,请参阅Kestrel 最大请求正文大小部分。 Kestrel 客户端连接限制可能还需要调整。

IFormFile 的空引用异常

如果控制器正在接受使用 IFormFile 上传的文件,但该值为 null,请确认 HTML 窗体指定的 multipart/form-data 值是否为 enctype。 如果未在

元素上设置此属性,则不会发生文件上传,并且任何绑定的 IFormFile 参数都为 null。 此外,请确认窗体数据中的上传命名是否与应用的命名相匹配。

数据流太长

本主题中的示例依赖于 MemoryStream 来保存已上传的文件的内容。 MemoryStream 的大小限制为 int.MaxValue。 如果应用的文件上传方案要求保存大于 50 MB 的文件内容,请使用另一种方法,该方法不依赖单个 MemoryStream 来保存已上传文件的内容。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

netcore 文件服务器,在 ASP.NET Core 中上传文件 的相关文章

  • 华为校招机试 - 单词重量(Java)

    题目描述 每个句子由多个单词组成 句子中的每个单词的长度都可能不一样 我们假设每个单词的长度Ni为该单词的重量 你需要做的就是给出整个句子的平均重量V 输入描述 无 输出描述 无 用例 输入 Who Love Solo 输出 3 67 说明
  • 算法相关-经典排序算法(goland实现)

    概述 插入排序 将未排序的元素同已排序的元素从后往前比较 带排序元素 a 被比较元素 b 如果a
  • C++内存管理机制

    1 C 内存管理机制 在C 中内存分为5个区 分别是堆 栈 自由存储区 全局 静态存储区和常量存储区 堆 heap 分配方式类似于链表 一般由程序员分配和释放 若程序员不释放 OS可能回收 分配方法 malloc new 释放方法 free
  • LINUX上wireshark无权限问题

    1 查找dumpcap目录 sudo find name dumpcap 2 增加wireshark组 sudo groupadd wireshark 3 将dumpcap目录权限给于wireshark组 并给于相关权限 sudo chgr
  • border-radius使用详解

    我在使用这个属性的时候 一般都是用在给div或者button加上一点圆角弧度 显得好看一点 或者用来画一个圆形div 用的稍微高级一点的 也就是用来画了一个右半圆来做为侧边栏的展开 收缩按钮 一 border radius使用 border
  • Java线程的6种状态及切换(透彻讲解)

    Java中线程的状态分为6种 1 初始 NEW 新创建了一个线程对象 但还没有调用start 方法 2 运行 RUNNABLE Java线程中将就绪 ready 和运行中 running 两种状态笼统的称为 运行 线程对象创建后 其他线程
  • 二进制文件反序列化错误

    二进制文件反序列化 出现错误 SerializationException was unhandled The ObjectManager found an invalid number of fixups This usually ind
  • 两小时快速入门 TypeScript 基础(一)工作流、基本类型、高级类型

    个人简介 个人主页 前端杂货铺 学习方向 主攻前端方向 也会涉及到服务端 Node js 等 个人状态 2023届本科毕业生 已拿多个前端 offer 秋招 未来打算 为中国的工业软件事业效力 n 年 推荐学习 前端面试宝典 Vue2 Vu
  • Splunk 优化之加速报表 Accelerate reports

    1 背景 有些客户的数据比较大 这个时候就会用到 报表的加速功能 Accelerate reports If your report has a large number of events and is slow to complete
  • 蓝桥杯-分巧克力(二分搜索)

    历届试题 分巧克力 时间限制 1 0s 内存限制 256 0MB 问题描述 儿童节那天有K位小朋友到小明家做客 小明拿出了珍藏的巧克力招待小朋友们 小明一共有N块巧克力 其中第i块是Hi x Wi的方格组成的长方形 为了公平起见 小明需要从
  • 程序无法启动ALL_BUILD 拒绝访问

    用cmake编译完opencv3 0后 发现编译没有问题 但尝试调试的时候报错 无法启动 ALL BUILD拒绝访问 调了很久才解决 方法是 卸载所有无关工程 只保留一个你需要的工程 这时候ZERO CHECK以及ALL BUILD都没有必
  • 什么是数字货币、数字金融 和区块链?

    从金融视角来说 区块链和数字货币 其实就是新一代的数字金融体系 数字金融体系 就是建立在区块链数字货币的金融基础设施上的 站在企业的角度 怎么来理解数字经济 我们知道工业经济驱动因素是化石燃料 数字经济驱动因素是数据 那么数据怎么去驱动一个
  • JAVA实习生刚进入公司一般会被安排做什么样的工作?

    新人进公司首先给你配置个人有邮箱和ip clone代码让你熟悉大概有一周左右 再在此之间 可能会有你的同事或者组长来给你大致讲一下项目的模块 架构 数据库 有的 公司让你看 不懂的让你去问他 针对于刚毕业的 还没有相关经验的可能会有所不同
  • 华为服务器bmc怎么传文件,华为服务器bmc配置

    华为服务器bmc配置 内容精选 换一换 通过华为云创建的ECS服务器默认使用华为云提供的内网DNS进行解析 内网DNS不影响ECS服务器对公网域名的访问 同时 还可以不经Internet 直接通过内网DNS访问其他云上服务内部地址 如OBS
  • 如何修改cmd控制台默认编码为utf-8,正确显示汉字

    首先我们打开在运行输入框等方式打开cmd窗口后 在窗口顶部右击选择属性 选中选项后会看到默认编码为gbk 然后我们在默认窗口路径内 输入chcp命令后回车 会输出图中的结果 936就表示gbk编码 然后在窗口中输入chcp 65001 65
  • ECharts 设置折线颜色和小圆点颜色

    ECharts 设置折线颜色只需要设置lineStyle的color即可 设置小圆点颜色只需要设置itemStyle的颜色即可 series name seriesName type line itemStyle normal color
  • Spark性能优化指南——基础篇

    前言 在大数据计算领域 Spark已经成为了越来越流行 越来越受欢迎的计算平台之一 Spark的功能涵盖了大数据领域的离线批处理 SQL类处理 流式 实时计算 机器学习 图计算等各种不同类型的计算操作 应用范围与前景非常广泛 在美团 大众点
  • 高德地图——步行导航

    高德地图 步行导航 插件 plugin AMap Walking 步行导航和驾驶导航几乎是一样的 唯一的不同便是导入的插件不同 步行导航的全程都是蓝色的 而驾驶导航线会有红色拥堵 绿色通畅的颜色
  • 实现java导出文件弹出下载框让用户选择路径

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 在实现导出文件时 弹出下载框主要是设置成文件流 stream类型的response 浏览器就会识别 然后弹出下载框让用户选择保存路径 这里总结三个方式 web struts

随机推荐

  • c# asp.net 如何在js文件中使用服务器变量,asp.net中JS,CS 调用后台变量的值多种方法...

    本文章介绍了关于asp net中JS CS 调用后台变量的值多种方法 有需了解的朋友可以参考一下 1 后台 Publicstringstr 123 最好为Public类型 直接在AspX前台页面HTML代码中要放的位置写入如下代码 2 用J
  • 解决${}EL表达式不起作用,无法获取数据,页面显示内容出错

    问题 EL表达式无法获取数据 解决办法 在jsp页面加入 这句话表示 可以使用EL 表达式 效果
  • html标签中src=“图片路径”,怎么用变量替换路径

    div style background image none bg0 gif bg5 gif div
  • C++中std::sort/std::stable_sort/std::partial_sort的区别及使用

    某些算法会重排容器中元素的顺序 如std sort 调用sort会重排输入序列中的元素 使之有序 它默认是利用元素类型的 lt 运算符来实现排序的 也可以重载sort的默认排序 即通过sort的第三个参数 此参数是一个谓词 predicat
  • 转载:IT项目管理-看板管理

    作为一个开发团队的管理者 例如当你是一个团队的项目经理的时候 任务的完成情况通常是你最关心的内容之一 比如说分配的任务是否能够按时间完成 整个项目的进度是否尚在计划之中 团队内的人是不是都在高效地工作 大家有没有什么困难 这些是你经常会关注
  • 无刷直流电机控制MATLAB仿真,使用Simulink进行无刷直流电机控制仿真

    这段时间刚开始接触Matlab中的Simulink仿真 我就结合自己的专业 利用Simulink进行了无刷直流电机的仿真 因为Simulink工具箱里面有很多可用的模块 所以建模过程变得非常简单 在Matlab界面中new gt model
  • MSP430F42X系列单片机SD16例程(16位AD采样)

    说明 该驱动程序库包含了常用的16位ADC SD16 操作与控制功能函数 如选择通道 设置信号放大倍数 设置数据格式 基准源输出开关等 以及常用采样函数 包括单通道采样 平均采样 多通道同时采样等 可以作为各种程序的底层驱动使用 要使用该库
  • 51单片机多机通信

    视频学习链接 https www bilibili com video BV1pi4y147A6 spm id from 333 880 my history page click vd source b91967c499b23106586
  • Zabbix的模板管理与配置

    Zabbix的模板管理与配置 一 查看默认模板的配置项 1 打开客户端信息配置界面 2 选择默认模板的监控项 二 服务端获取客户端的监控项 1 获取客户端系统相关监控项 2 获取客户端硬盘信息等相关监控项 三 创建自定义监控项的key 1
  • 如何在IDEA中使用JDBC

    如何在IDEA中使用JDBC 摘要 安装JDK及IDEA mysql下载安装及预处理 JDBC驱动下载 新建IDEA项目 添加JDBC驱动文件至项目 编写java测试语句 摘要 本文主要介绍了如何用IDEA新建一个java项目 并用JDBC
  • Docker私服之Harbor搭建全过程【安装+启动+jar镜像构建、推送、拉取、运行】

    1 docker安装 docker compose docker和docker compose安装参考链接 2 harbor安装 harbor下载 harbor offline installer v2 5 3 tgz 我下载的版本是2 5
  • 芯片制造系列全流程:设计、制造、封测

    目录 芯片制造系列全流程 简 一 芯片制造全流程简介 二 芯片设计 三 芯片制造 四 封装测试 芯片目前分为三个主要环节 分别是设计 制程 封测 设计水平 制造这一块 最后说说封测这一块 芯片设计 芯片制造 封装测试完整解读 01 芯 片
  • 手把手教你安装CUDA(一看就会)

    1 背景 学习深度学习的话 肯定需要安装PyTorch和TensorFlow 安装这两个深度学习框架之前得安装CUDA CUDA是什么 CUDA是一个并行计算平台和编程模型 能够使得使用GPU进行通用计算变得简单和优雅 Nvidia官方提供
  • 树状数组笔记

    数组 前缀和 树状数组的区别 数组 修改某点O 1 求区间O n 前缀和 修改某点O n 求区间O 1 树状数组 修改某点O logn 求区间O logn 树状数组采取折中的方式 降低整体的时间复杂度 由于算法复杂度取决于最坏的情况的复杂度
  • 1.vs2019 配置Eigen

    目录 一 下载Eigen 二 创建工程 三 测试代码 四 运行结果 一 下载Eigen 下载地址 http eigen tuxfamily org index php title Main Page Download 下载后 将文件解压 二
  • Python--pytesseract验证码识别处理实例

    linux ubuntu系统 安装过程 pytesser 调用了 tesseract 因此需要安装 tesseract 安装 tesseract 需要安装 leptonica 否则编译tesseract 的时候出现 configure er
  • mysql 自定义函数 if not exists_IF配合AND、OR以及NOT函数使用,可以解决工作中的不少难题...

    前面小编已经分别介绍了逻辑判断函数IF AND OR及NOT的用法 同时也提到它们比较少单独使用 那么 这篇文章我们就来介绍一下IF分别和AND OR及NOT的配合用法 1 函数定义回顾 首先来回顾下这4个逻辑判断函数的定义 1 IF函数
  • 每日一题:整齐的数组

    整齐的数组 题目 Daimayuan Online Judge 每一次可以选择一个ai减去k 可以进行若干次操作 使得所有数变相同 说明跟顺序无关 可以从小到大排个序 k大于等于1 说明了每个数只能变小不能变大 那么每个数只能变得和最小的那
  • Android-系统分享使用小结

    Android 系统分享使用小结 概述 如何进行分享 如何筛选分享项 如何区分部分APP下不同分享界面 以微信为例 如何还原过滤前APP分享途径的描述 概述 说到分享 有很多第三方的SDK可供使用 比如友盟 mob都很好用 虽然集成相对容易
  • netcore 文件服务器,在 ASP.NET Core 中上传文件

    ASP NET Core 支持使用缓冲的模型绑定 针对较小文件 和无缓冲的流式传输 针对较大文件 上传一个或多个文件 安全注意事项 向用户提供向服务器上传文件的功能时 必须格外小心 攻击者可能会尝试执行以下操作 执行拒绝服务攻击 上传病毒或