添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

本文提供了有关最大程度地提高 ASP.NET Core 应用的性能和可靠性的准则。

充分利用缓存

缓存在本文的多个部分中进行了讨论。 有关详细信息,请参阅 ASP.NET Core 中的缓存概述

了解热代码路径

在本文中,热代码路径定义为经常调用并形成大量执行时间的代码路径。 热代码路径通常会限制应用横向扩展和性能,在本文的多个部分中进行了讨论。

避免阻塞调用

ASP.NET Core 应用应设计为可同时处理许多请求。 异步 API 允许较小线程池处理数千个并发请求,无需等待阻塞调用。 线程可以处理另一个请求,而不是等待长时间运行的同步任务完成。

ASP.NET Core 应用中的一个常见性能问题是阻塞可以异步进行的调用。 许多同步阻塞调用都会导致 线程池饥饿 和响应时间降低。

请勿通过调用 Task.Wait Task<TResult>.Result 来阻止异步执行。 请勿获取常见代码路径中的锁。 当构建为并行运行代码时,ASP.NET Core 应用的性能最佳。 请勿调用 Task.Run 并立即等待它。 ASP.NET Core 已经在普通线程池线程上运行应用代码,因此调用 Task.Run 只会导致不必要的额外线程池计划。 即使计划的代码会阻止某个线程, Task.Run 也不会阻止该线程。

  • 务必使 热代码路径 异步。
  • 如果有异步 API 可用,则务必异步调用数据访问、I/O 和长时间运行的操作 API。
  • 请勿使用 Task.Run 使同步 API 异步。
  • 务必使控制器/Razor Page 操作异步。 为了获益于 async/await 模式,整个调用堆栈都是异步的。
  • 探查器(例如 PerfView )可用于查找频繁添加到 线程池 中的线程。 Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start 事件指示添加到线程池的线程。

    跨多个较小页面返回大型集合

    网页不应一次加载大量数据。 返回对象集合时,请考虑它是否会导致性能问题。 确定设计是否可能会产生以下不良结果:

  • OutOfMemoryException 或占用大量内存
  • 线程池资源不足(请参阅以下有关 IAsyncEnumerable<T> 的注解)
  • 响应时间缓慢
  • 频繁的垃圾回收
  • 请添加分页以缓解以上情形。 使用页面大小和页面索引参数时,开发人员应支持返回部分结果的设计。 当需要详尽结果时,应使用分页来异步填充结果批次,以避免锁定服务器资源。

    有关分页和限制返回的记录数的详细信息,请参阅:

  • 性能注意事项
  • 将分页添加到 ASP.NET Core 应用
  • 返回 IEnumerable<T> IAsyncEnumerable<T>

    从操作返回 IEnumerable<T> 会导致序列化程序同步集合迭代。 因此会阻止调用,并且可能会导致线程池资源不足。 若要避免同步枚举,请在返回可枚举内容前使用 ToListAsync

    从 ASP.NET Core 3.0 开始, IAsyncEnumerable<T> 可用作异步枚举的 IEnumerable<T> 的替代方法。 有关详细信息,请参阅 控制器操作返回类型

    最大程度减少大型对象分配

    .NET Core 垃圾回收器 在 ASP.NET Core 应用中自动管理内存分配和释放。 自动垃圾回收通常意味着开发人员无需担心如何或何时释放内存。 但是,清理未引用的对象会占用 CPU 时间,因此开发人员应最大限度减少 热代码路径 中的对象分配。 垃圾回收在大型对象(> = 85,000 字节)上成本特别高昂。 大型对象存储在 大型对象堆 上,需要完整(第 2 代)垃圾回收才能清理。 与第 0 代和第 1 代回收不同,第 2 代回收需要临时暂停应用执行。 频繁分配和取消分配大型对象可能会导致性能不一致。

  • 请考虑缓存经常使用的大型对象。 缓存大型对象会阻止进行成本高昂的分配。
  • 务必使用 ArrayPool<T> 形成池缓冲区以存储大型数组。
  • 请勿在 热代码路径 上分配许多生存期较短的大型对象。
  • 可以通过在 PerfView 中查看垃圾回收 (GC) 统计信息并检查以下内容来诊断内存问题(如前面的问题):

  • 垃圾回收暂停时间。
  • 花费在垃圾回收上的处理器时间百分比。
  • 第 0 代、第 1 代和第 2 代的垃圾回收量。
  • 有关详细信息,请参阅 垃圾回收和性能

    优化数据访问和 I/O

    与数据存储和其他远程服务的交互通常是 ASP.NET Core 应用的最慢部分。 高效读取和写入数据对于良好的性能至关重要。

  • 请异步调用所有数据访问 API。
  • 请勿检索不需要的数据。 编写查询以便仅返回当前 HTTP 请求所需的数据。
  • 如果可接受稍微过时的数据,请考虑缓存从数据库或远程服务检索的经常访问的数据。 根据方案使用 MemoryCache DistributedCache 。 有关详细信息,请参阅 ASP.NET Core 中的响应缓存
  • 请尽量缩短网络往返。 目标是在单个调用而不是多个调用中检索所需数据。
  • 当出于只读目的访问数据时, 在Entity Framework Core中使用 无跟踪查询 。 EF Core 可以更有效地返回无跟踪查询的结果。
  • 请筛选和聚合 LINQ 查询(例如使用 .Where .Select .Sum 语句),以便数据库执行筛选。
  • 务必考虑到 EF Core 会在客户端上解析一些查询运算符,这可能会导致查询执行效率低下。 有关详细信息,请参阅 客户端评估性能问题
  • 请勿对集合使用投影查询,这可能会导致执行“N + 1”个 SQL 查询。 有关详细信息,请参阅 相关子查询优化
  • 以下方法可以提高大规模应用的性能:

  • DbContext 池
  • 显式编译的查询
  • 建议在提交基本代码之前衡量前面高性能方法的影响。 已编译查询的额外复杂性可能无法证明性能改进的合理性。

    通过使用 Application Insights 或分析工具查看访问数据所用的时间,可以检测到查询问题。 大多数数据库还提供有关频繁执行的查询的统计信息。

    与 HttpClientFactory 之间的池 HTTP 连接

    尽管 HttpClient 实现了 IDisposable 接口,不过它设计为可重用。 关闭的 HttpClient 实例使套接字在短时间内以 TIME_WAIT 状态保持打开。 如果经常使用创建和释放 HttpClient 对象的代码路径,则应用可能会耗尽可用的套接字。 在 ASP.NET Core 2.1 中引入了 HttpClientFactory ,以作为此问题的解决方案。 它会处理池 HTTP 连接以优化性能和可靠性。 有关详细信息,请参阅 使用 HttpClientFactory 实现可复原的 HTTP 请求

  • 请勿直接创建和释放 HttpClient 实例。
  • 请勿使用 HttpClientFactory 检索 HttpClient 实例。 有关详细信息,请参阅 使用 HttpClientFactory 实现可复原的 HTTP 请求
  • 使常用代码路径保持快速

    你希望所有代码都可快速执行。 经常调用的代码路径是优化的关键。 其中包括:

  • 应用请求处理管道中的中间件组件,尤其是在管道中早期运行的中间件。 这些组件对性能具有很大影响。
  • 对每个请求都执行或是按请求执行多次的代码。 例如,自定义日志记录、授权处理程序或暂时性服务的初始化。
  • 请勿将自定义中间件组件用于长时间运行的任务。
  • 请使用性能分析工具(例如 Visual Studio 诊断工具 PerfView )标识 热代码路径
  • 在 HTTP 请求外部完成长时间运行的任务

    对 ASP.NET Core 应用进行的大多数请求可以由调用必要服务并返回 HTTP 响应的控制器或页面模型进行处理。 对于涉及长时间运行的任务的一些请求,最好使整个请求-响应过程异步进行。

  • 在普通 HTTP 请求处理过程中,请勿等待长时间运行的任务完成。
  • 请考虑使用 后台服务 处理长时间运行的请求,或使用 Azure 函数 进行进程外处理。 在进程外完成工作对于 CPU 密集型任务尤其有利。
  • 请使用实时通信选项(如 SignalR )以异步方式与客户端通信。
  • 缩小客户端资产

    具有复杂前端的 ASP.NET Core 应用会经常处理许多 JavaScript、CSS 或图像文件。 初始加载请求的性能可以通过以下方式得到提高:

  • 捆绑,即将多个文件合并为一个文件。
  • 缩小,即通过删除空格和注释来减小文件的大小。
  • 务必使用 捆绑和缩小准则 ,其中提及了兼容工具,并演示如何使用 ASP.NET Core 的 environment 标记处理 Development Production 环境。
  • 请考虑使用其他第三方工具(如 Webpack )进行复杂客户端资产管理。
  • 减小响应大小通常可显著提高应用的响应速度。 减小有效负载大小的一种方式是压缩应用的响应。 有关详细信息,请参阅 响应压缩

    使用最新 ASP.NET Core 版本

    每个新版本的 ASP.NET Core 都包含性能改进。 .NET Core 和 ASP.NET Core 中的优化意味着较新版本的性能通常优于较旧版本。 例如,.NET Core 2.1 添加了对已编译正则表达式的支持,可受益于 Span<T> 。 ASP.NET Core 2.2 添加了对 HTTP/2 的支持。 ASP.NET Core 3.0 添加了许多改进 ,可减少内存使用量并提高吞吐量。 如果性能是优先事项,请考虑升级到当前版本的 ASP.NET Core。

    尽量减少异常

    异常应很少出现。 相对于其他代码流模式,引发和捕获异常的速度较慢。 因此,不应使用异常来控制正常程序流。

  • 请勿将引发或捕获异常用作正常程序流的一种方法(尤其是在 热代码路径 中)。
  • 请在应用中包含逻辑,以检测和处理会导致异常的状况。
  • 对于不寻常或意外状况,请引发或捕获异常。
  • 应用诊断工具(如 Application Insights)可帮助识别应用中可能会影响性能的常见异常。

    避免对 HttpRequest/HttpResponse 正文进行同步读取或写入

    ASP.NET Core 中的所有 I/O 都是异步的。 服务器会实现 Stream 接口,该接口同时具有同步和异步重载。 应首选异步重载以避免阻止线程池线程。 阻止线程可能会导致线程池资源不足。

    请勿这样做:下面的示例使用 ReadToEnd 。 它会阻止当前线程等待结果。 下面是 异步中同步 的示例。

    public class BadStreamReaderController : Controller
        [HttpGet("/contoso")]
        public ActionResult<ContosoData> Get()
            var json = new StreamReader(Request.Body).ReadToEnd();
            return JsonSerializer.Deserialize<ContosoData>(json);
    

    在上面的代码中,Get 将整个 HTTP 请求正文同步读取到内存中。 如果客户端上传速度缓慢,则应用会进行异步中同步。 应用进行异步中同步是因为 Kestrel 不支持同步读取。

    请这样做:下面的示例使用 ReadToEndAsync,在读取时不会阻止线程。

    public class GoodStreamReaderController : Controller
        [HttpGet("/contoso")]
        public async Task<ActionResult<ContosoData>> Get()
            var json = await new StreamReader(Request.Body).ReadToEndAsync();
            return JsonSerializer.Deserialize<ContosoData>(json);
    

    上面的代码会将整个 HTTP 请求正文同步读取到内存中。

    如果请求较大,则将整个 HTTP 请求正文读取到内存中可能会导致内存不足 (OOM) 状况。 OOM 可能会导致拒绝服务。 有关详细信息,请参阅本文中的避免将大型请求正文或响应正文读取到内存中

    请这样做:下面的示例使用非缓冲请求正文完全异步进行:

    public class GoodStreamReaderController : Controller
        [HttpGet("/contoso")]
        public async Task<ActionResult<ContosoData>> Get()
            return await JsonSerializer.DeserializeAsync<ContosoData>(Request.Body);
    

    上面的代码将请求正文异步反序列化为 C# 对象。

    首选 ReadFormAsync 而不是 Request.Form

    请使用 HttpContext.Request.ReadFormAsync,而不是 HttpContext.Request.FormHttpContext.Request.Form 只有在以下状况下才能安全读取:

  • 通过调用 ReadFormAsync 读取了窗体,并且
  • 在使用 HttpContext.Request.Form 读取缓存的窗体值
  • 请勿这样做:下面的示例使用 HttpContext.Request.FormHttpContext.Request.Form 使用异步中同步,可能会导致线程池资源不足。

    public class BadReadController : Controller
        [HttpPost("/form-body")]
        public IActionResult Post()
            var form =  HttpContext.Request.Form;
            Process(form["id"], form["name"]);
            return Accepted();
    

    请这样做:下面的示例使用 HttpContext.Request.ReadFormAsync 异步读取窗体正文。

    public class GoodReadController : Controller
        [HttpPost("/form-body")]
        public async Task<IActionResult> Post()
           var form = await HttpContext.Request.ReadFormAsync();
            Process(form["id"], form["name"]);
            return Accepted();
    

    避免将大型请求正文或响应正文读取到内存中

    在 .NET 中,每个大于或等于 85,000 字节的对象分配最终都位于大型对象堆 (LOH) 中。 大型对象在两方面的成本十分高昂:

  • 分配成本较高,因为必须清除新分配的大型对象的内存。 CLR 可保证清除所有新分配的对象的内存。
  • LOH 与堆的其余部分一起收集。 LOH 需要完整垃圾回收第 2 代收集
  • 博客文章简洁地描述了问题:

    分配大型对象时,它标记为第 2 代对象。 而不是与小型对象一样标记为第 0 代。 后果是,如果在 LOH 中内存不足,GC 会清理整个托管堆,而不仅是 LOH。 因此它会清理第 0 代、第 1 代和第 2 代,包括 LOH。 这称为完整垃圾回收,是最耗时的垃圾回收。 对于许多应用程序,这可以接受。 但肯定不适用于高性能 Web 服务器,在这类服务器中,需要极少的大内存缓冲区来处理普通 Web 请求(从套接字读取、解压缩和解码 JSON 等)。

    将大型请求或响应正文存储到单个 byte[]string 中:

  • 可能会导致 LOH 中的空间快速用尽。
  • 由于运行完整 GC,可能会导致应用出现性能问题。
  • 使用同步数据处理 API

    使用仅支持同步读取和写入的序列化程序/反序列化程序(例如 Json.NET)时:

  • 先将数据异步缓冲到内存中,然后再将数据传递到序列化程序/反序列化程序。
  • 如果请求较大,则可能会导致内存不足 (OOM) 状况。 OOM 可能会导致拒绝服务。 有关详细信息,请参阅本文中的避免将大型请求正文或响应正文读取到内存中

    ASP.NET Core 3.0 默认使用 System.Text.Json 进行 JSON 序列化。 System.Text.Json

  • 以异步方式读取和写入 JSON。
  • 针对 UTF-8 文本进行了优化。
  • 通常比 Newtonsoft.Json 性能更高。
  • 请勿将 IHttpContextAccessor.HttpContext 存储在字段中

    从请求线程访问时,IHttpContextAccessor.HttpContext 会返回活动请求的 HttpContextIHttpContextAccessor.HttpContext 不应存储在字段或变量中。

    请勿这样做:下面的示例将 HttpContext 存储在字段中,然后尝试在以后使用它。

    public class MyBadType
        private readonly HttpContext _context;
        public MyBadType(IHttpContextAccessor accessor)
            _context = accessor.HttpContext;
        public void CheckAdmin()
            if (!_context.User.IsInRole("admin"))
                throw new UnauthorizedAccessException("The current user isn't an admin");
    

    上面的代码经常在构造函数中捕获 Null 或不正确的 HttpContext

    请这样做:下面的示例:

  • IHttpContextAccessor 存储在字段中。
  • 在正确的时间使用 HttpContext 字段并检查是否存在 null
  • public class MyGoodType
        private readonly IHttpContextAccessor _accessor;
        public MyGoodType(IHttpContextAccessor accessor)
            _accessor = accessor;
        public void CheckAdmin()
            var context = _accessor.HttpContext;
            if (context != null && !context.User.IsInRole("admin"))
                throw new UnauthorizedAccessException("The current user isn't an admin");
    

    请勿从多个线程访问 HttpContext

    HttpContext 不是线程安全型。 并行从多个线程访问 HttpContext 可能会导致意外的行为,例如挂起、崩溃和数据损坏。

    请勿这样做:下面的示例进行三个并行请求,并记录传出 HTTP 请求之前和之后的传入请求路径。 请求路径从多个线程进行访问(可能是并行访问)。

    public class AsyncBadSearchController : Controller
        [HttpGet("/search")]
        public async Task<SearchResults> Get(string query)
            var query1 = SearchAsync(SearchEngine.Google, query);
            var query2 = SearchAsync(SearchEngine.Bing, query);
            var query3 = SearchAsync(SearchEngine.DuckDuckGo, query);
            await Task.WhenAll(query1, query2, query3);
            var results1 = await query1;
            var results2 = await query2;
            var results3 = await query3;
            return SearchResults.Combine(results1, results2, results3);
        private async Task<SearchResults> SearchAsync(SearchEngine engine, string query)
            var searchResults = _searchService.Empty();
                _logger.LogInformation("Starting search query from {path}.", 
                                        HttpContext.Request.Path);
                searchResults = _searchService.Search(engine, query);
                _logger.LogInformation("Finishing search query from {path}.", 
                                        HttpContext.Request.Path);
            catch (Exception ex)
                _logger.LogError(ex, "Failed query from {path}", 
                                 HttpContext.Request.Path);
            return await searchResults;
    

    请这样做:下面的示例在进行三个并行请求之前,从传入请求复制所有数据。

    public class AsyncGoodSearchController : Controller
        [HttpGet("/search")]
        public async Task<SearchResults> Get(string query)
            string path = HttpContext.Request.Path;
            var query1 = SearchAsync(SearchEngine.Google, query,
                                     path);
            var query2 = SearchAsync(SearchEngine.Bing, query, path);
            var query3 = SearchAsync(SearchEngine.DuckDuckGo, query, path);
            await Task.WhenAll(query1, query2, query3);
            var results1 = await query1;
            var results2 = await query2;
            var results3 = await query3;
            return SearchResults.Combine(results1, results2, results3);
        private async Task<SearchResults> SearchAsync(SearchEngine engine, string query,
                                                      string path)
            var searchResults = _searchService.Empty();
                _logger.LogInformation("Starting search query from {path}.",
                                       path);
                searchResults = await _searchService.SearchAsync(engine, query);
                _logger.LogInformation("Finishing search query from {path}.", path);
            catch (Exception ex)
                _logger.LogError(ex, "Failed query from {path}", path);
            return await searchResults;
    

    请求完成之后,请勿使用 HttpContext

    HttpContext 仅在 ASP.NET Core 管道中存在活动 HTTP 请求时有效。 整个 ASP.NET Core 管道是执行每个请求的异步委托链。 从此链返回的 Task 完成后,会回收 HttpContext

    请勿这样做:下面的示例使用 async void,它使 HTTP 请求在达到第一个 await 时完成:

  • 使用 async void 在 ASP.NET Core 应用中始终是不良做法。
  • 在 HTTP 请求完成之后,示例代码访问 HttpResponse
  • 延迟访问导致进程崩溃。
  • public class AsyncBadVoidController : Controller
        [HttpGet("/async")]
        public async void Get()
            await Task.Delay(1000);
            // The following line will crash the process because of writing after the 
            // response has completed on a background thread. Notice async void Get()
            await Response.WriteAsync("Hello World");
    

    请这样做:下面的示例将 Task 返回到框架,因此在操作完成之前,HTTP 请求不会完成。

    public class AsyncGoodTaskController : Controller
        [HttpGet("/async")]
        public async Task Get()
            await Task.Delay(1000);
            await Response.WriteAsync("Hello World");
    

    请勿在后台线程中捕获 HttpContext

    请勿这样做:下面的示例演示闭包从 Controller 属性捕获 HttpContext。 这是一种不良做法,因为工作项可能:

  • 在请求范围之外运行。
  • 尝试读取错误的 HttpContext
  • [HttpGet("/fire-and-forget-1")]
    public IActionResult BadFireAndForget()
        _ = Task.Run(async () =>
            await Task.Delay(1000);
            var path = HttpContext.Request.Path;
            Log(path);
        return Accepted();
    

    请这样做:下面的示例:

  • 在请求期间复制后台任务中所需的数据。
  • 不从控制器引用任何内容。
  • [HttpGet("/fire-and-forget-3")]
    public IActionResult GoodFireAndForget()
        string path = HttpContext.Request.Path;
        _ = Task.Run(async () =>
            await Task.Delay(1000);
            Log(path);
        return Accepted();
    

    后台任务应作为托管服务实现。 有关详细信息,请参阅使用托管服务的后台任务

    请勿在后台线程上捕获注入到控制器中的服务

    请勿这样做:下面的示例演示闭包从 Controller 操作参数捕获 DbContext。 这是一种不良做法。 工作项可能在请求范围之外运行。 ContosoDbContext 的范围限定为请求,从而导致 ObjectDisposedException

    [HttpGet("/fire-and-forget-1")]
    public IActionResult FireAndForget1([FromServices]ContosoDbContext context)
        _ = Task.Run(async () =>
            await Task.Delay(1000);
            context.Contoso.Add(new Contoso());
            await context.SaveChangesAsync();
        return Accepted();
    

    请这样做:下面的示例:

  • 注入 IServiceScopeFactory 以便在后台工作项中创建范围。 IServiceScopeFactory 是单一实例。
  • 在后台线程中创建新的依赖项注入范围。
  • 不从控制器引用任何内容。
  • 不从传入请求捕获 ContosoDbContext
  • [HttpGet("/fire-and-forget-3")]
    public IActionResult FireAndForget3([FromServices]IServiceScopeFactory 
                                        serviceScopeFactory)
        _ = Task.Run(async () =>
            await Task.Delay(1000);
            await using (var scope = serviceScopeFactory.CreateAsyncScope())
                var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();
                context.Contoso.Add(new Contoso());
                await context.SaveChangesAsync();                                        
        return Accepted();
    

    以下突出显示的代码:

  • 在后台操作生存期内创建范围,并解析其中的服务。
  • 从正确的范围使用 ContosoDbContext
  • [HttpGet("/fire-and-forget-3")]
    public IActionResult FireAndForget3([FromServices]IServiceScopeFactory 
                                        serviceScopeFactory)
        _ = Task.Run(async () =>
            await Task.Delay(1000);
            await using (var scope = serviceScopeFactory.CreateAsyncScope())
                var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();
                context.Contoso.Add(new Contoso());
                await context.SaveChangesAsync();                                        
        return Accepted();
    

    在响应正文启动之后,请勿修改状态代码或标头

    ASP.NET Core 不会缓冲 HTTP 响应正文。 首次写入响应时:

  • 标头与正文的该区块一起发送到客户端。
  • 无法再更改响应标头。
  • 请勿这样做:下面的代码尝试在响应已启动之后添加响应标头:

    app.Use(async (context, next) =>
        await next();
        context.Response.Headers["test"] = "test value";
    

    在上面的代码中,如果将 next() 写入响应,则 context.Response.Headers["test"] = "test value"; 会引发异常。

    请这样做:下面的示例在修改标头之前检查 HTTP 响应是否已启动。

    app.Use(async (context, next) =>
        await next();
        if (!context.Response.HasStarted)
            context.Response.Headers["test"] = "test value";
    

    请这样做:下面的示例使用 HttpResponse.OnStarting 在将响应标头刷新到客户端之前设置标头。

    通过检查响应是否未启动,可以在写入响应标头之前注册将调用的回调。 检查响应是否未启动:

  • 提供实时追加或替代标头的能力。
  • 不需要了解管道中的下一个中间件。
  • app.Use(async (context, next) =>
        context.Response.OnStarting(() =>
            context.Response.Headers["someheader"] = "somevalue";
            return Task.CompletedTask;
        await next();
    

    如果已开始向响应正文写入,请勿调用 next()

    只有在组件可以处理和操作响应时,才应调用组件。

    将进程内托管与 IIS 结合使用

    使用进程内托管,ASP.NET Core 在与其 IIS 工作进程相同的进程中运行。 进程内托管可提供比进程外托管更高的性能,因为请求不会通过环回适配器进行代理。 环回适配器是一个网络接口,用于将传出的网络流量返回给同一计算机。 IIS 使用 Windows 进程激活服务 (WAS) 处理进程管理。

    在 ASP.NET Core 3.0 及更高版本中,项目默认为进程内托管模型。

    有关详细信息,请参阅 使用 IIS 在 Windows 上托管 ASP.NET Core

    请勿假设 HttpRequest.ContentLength 不为 null

    如果未收到 Content-Length 标头,则 HttpRequest.ContentLength 为 null。 如果为 null,则表示请求正文的长度未知;这并不意味着长度为零。 因为所有与 null 的比较(除了 ==)都返回 false,例如,当请求正文大小大于 1024 时,比较 Request.ContentLength > 1024 可能返回 false。 不知道这可能会导致应用中出现安全漏洞。 你可能会认为你在防范过大的请求,而你却没有。

    有关详细信息,请参阅此 StackOverflow 答案

    可靠的 Web 应用模式

    请观看适用于 .NET 的可靠 Web 应用模式YouTube 视频文章,了解如何创建新式、可靠、高性能、可测试、经济高效且可缩放的 ASP.NET Core 应用,无论是从头开始创建还是重构现有应用。

    即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:https://aka.ms/ContentUserFeedback

    提交和查看相关反馈