从 ASP.NET Core 2.2 迁移到 3.0

作者:Scott AddieRick Anderson

本文介绍如何将现有 ASP.NET Core 2.2 项目更新为 ASP.NET Core 3.0。 创建新 3.0 ASP.NET Core 可能有助于:

  • 与 ASP.NET Core 2.2 代码进行比较。
  • 将相关更改复制到 ASP.NET Core 3.0 项目。

先决条件

在 global.json 中更新 .NET Core SDK 版本

如果解决方案依靠 global.json 文件来面向特定 .NET Core SDK 版本,请将其 version 属性更新为计算机上安装的 3.0 版本:

{
  "sdk": {
    "version": "3.0.100"
  }
}

更新项目文件

更新目标框架

ASP.NET Core 3.0 以及更高版本只能在 .NET Core 中运行。 将目标框架名字对象 (TFM) 设置为 netcoreapp3.0

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

</Project>

删除已过时的包引用

不会为 ASP.NET Core 3.0 生成大量 NuGet 包。 应从项目文件中删除此类包引用。 请考虑以下适用于 ASP.NET Core 2.2 Web 应用的项目文件:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App"/>
    <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
  </ItemGroup>

</Project>

针对 ASP.NET Core 3.0 更新后的项目文件:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

</Project>

更新后的 ASP.NET Core 3.0 项目文件:

  • <PropertyGroup> 中:

    • 将 TFM 更新为 netcoreapp3.0
    • 删除了 <AspNetCoreHostingModel> 元素。 有关详细信息,请参阅本文档中的进程内托管模型
  • <ItemGroup> 中:

    • 删除了 Microsoft.AspNetCore.App。 有关详细信息,请参阅本文档中的框架引用
    • 删除了 Microsoft.AspNetCore.Razor.Design,它处于以下不再生成的包列表中。

若要查看不再生成的包的完整列表,请选择以下展开列表:

单击可展开不再生成的包列表
  • Microsoft.AspNetCore
  • Microsoft.AspNetCore.All
  • Microsoft.AspNetCore.App
  • Microsoft.AspNetCore.Antiforgery
  • Microsoft.AspNetCore.Authentication
  • Microsoft.AspNetCore.Authentication.Abstractions
  • Microsoft.AspNetCore.Authentication.Cookies
  • Microsoft.AspNetCore.Authentication.Core
  • Microsoft.AspNetCore.Authentication.OAuth
  • Microsoft.AspNetCore.Authorization.Policy
  • Microsoft.AspNetCore.CookiePolicy
  • Microsoft.AspNetCore.Cors
  • Microsoft.AspNetCore.Diagnostics
  • Microsoft.AspNetCore.Diagnostics.HealthChecks
  • Microsoft.AspNetCore.HostFiltering
  • Microsoft.AspNetCore.Hosting
  • Microsoft.AspNetCore.Hosting.Abstractions
  • Microsoft.AspNetCore.Hosting.Server.Abstractions
  • Microsoft.AspNetCore.Http
  • Microsoft.AspNetCore.Http.Abstractions
  • Microsoft.AspNetCore.Http.Connections
  • Microsoft.AspNetCore.Http.Extensions
  • Microsoft.AspNetCore.HttpOverrides
  • Microsoft.AspNetCore.HttpsPolicy
  • Microsoft.AspNetCore.Identity
  • Microsoft.AspNetCore.Localization
  • Microsoft.AspNetCore.Localization.Routing
  • Microsoft.AspNetCore.Mvc
  • Microsoft.AspNetCore.Mvc.Abstractions
  • Microsoft.AspNetCore.Mvc.Analyzers
  • Microsoft.AspNetCore.Mvc.ApiExplorer
  • Microsoft.AspNetCore.Mvc.Api.Analyzers
  • Microsoft.AspNetCore.Mvc.Core
  • Microsoft.AspNetCore.Mvc.Cors
  • Microsoft.AspNetCore.Mvc.DataAnnotations
  • Microsoft.AspNetCore.Mvc.Formatters.Json
  • Microsoft.AspNetCore.Mvc.Formatters.Xml
  • Microsoft.AspNetCore.Mvc.Localization
  • Microsoft.AspNetCore.Mvc.Razor
  • Microsoft.AspNetCore.Mvc.Razor.ViewCompilation
  • Microsoft.AspNetCore.Mvc.RazorPages
  • Microsoft.AspNetCore.Mvc.TagHelpers
  • Microsoft.AspNetCore.Mvc.ViewFeatures
  • Microsoft.AspNetCore.Razor
  • Microsoft.AspNetCore.Razor.Runtime
  • Microsoft.AspNetCore.Razor.Design
  • Microsoft.AspNetCore.ResponseCaching
  • Microsoft.AspNetCore.ResponseCaching.Abstractions
  • Microsoft.AspNetCore.ResponseCompression
  • Microsoft.AspNetCore.Rewrite
  • Microsoft.AspNetCore.Routing
  • Microsoft.AspNetCore.Routing.Abstractions
  • Microsoft.AspNetCore.Server.HttpSys
  • Microsoft.AspNetCore.Server.IIS
  • Microsoft.AspNetCore.Server.IISIntegration
  • Microsoft.AspNetCore.Server.Kestrel
  • Microsoft.AspNetCore.Server.Kestrel.Core
  • Microsoft.AspNetCore.Server.Kestrel.Https
  • Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions
  • Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
  • Microsoft.AspNetCore.Session
  • Microsoft.AspNetCore.SignalR
  • Microsoft.AspNetCore.SignalR.Core
  • Microsoft.AspNetCore.StaticFiles
  • Microsoft.AspNetCore.WebSockets
  • Microsoft.AspNetCore.WebUtilities
  • Microsoft.Net.Http.Headers

查看中断性变更

查看中断性变更

框架引用

通过上面列出的包提供的 ASP.NET Core 功能作为 Microsoft.AspNetCore.App 共享框架的一部分提供。 共享框架是安装在计算机上并包括运行时组件和目标包的一组程序集(.dll 文件)。 有关详细信息,请参阅共享框架

  • 面向 Microsoft.NET.Sdk.Web SDK 的项目隐式引用 Microsoft.AspNetCore.App 框架。

    对于这些项目,不需要其他引用:

    <Project Sdk="Microsoft.NET.Sdk.Web">
      <PropertyGroup>
        <TargetFramework>netcoreapp3.0</TargetFramework>
      </PropertyGroup>
        ...
    </Project>
    
  • 面向 Microsoft.NET.SdkMicrosoft.NET.Sdk.Razor SDK 的项目应将显式 FrameworkReference 添加到 Microsoft.AspNetCore.App

    <Project Sdk="Microsoft.NET.Sdk.Razor">
      <PropertyGroup>
        <TargetFramework>netcoreapp3.0</TargetFramework>
      </PropertyGroup>
    
      <ItemGroup>
        <FrameworkReference Include="Microsoft.AspNetCore.App" />
      </ItemGroup>
        ...
    </Project>
    

使用 Docker 的依赖于框架的生成

如果控制台应用使用的包依赖于 ASP.NET Core 共享框架,则这些应用的依赖于框架的生成可能会导致以下运行时错误:

It was not possible to find any compatible framework version
The specified framework 'Microsoft.AspNetCore.App', version '3.0.0' was not found.
  - No frameworks were found.

Microsoft.AspNetCore.App 是包含 ASP.NET Core 运行时的共享框架,仅在 dotnet/core/aspnet Docker 映像中存在。 3.0 SDK 不包括在共享框架中提供的库的重复副本,从而减小使用 ASP.NET Core 的依赖于框架的生成大小。 这可能会节省多达 18 MB,但要求存在/安装 ASP.NET Core 运行时才能运行应用。

若要确定应用是否在 ASP.NET Core共享框架上 (直接或间接) 依赖项,请检查runtimeconfig.json在应用生成/发布过程中生成的文件。 以下 JSON 文件显示 ASP.NET Core共享框架的依赖项:

{
  "runtimeOptions": {
    "tfm": "netcoreapp3.0",
    "framework": {
      "name": "Microsoft.AspNetCore.App",
      "version": "3.0.0"
    },
    "configProperties": {
      "System.GC.Server": true
    }
  }
}

如果应用使用 Docker,请使用包含 ASP.NET Core 3.0 的基础映像。 例如,docker pull mcr.microsoft.com/dotnet/core/aspnet:3.0

为删除的程序集添加包引用

ASP.NET Core 3.0 删除了一些以前作为 Microsoft.AspNetCore.App 包引用一部分的程序集。 若要直观显示已删除的程序集,请比较两个共享框架文件夹。 例如,版本 2.2.7 与 3.0.0 的比较:

共享框架程序集比较

若要继续使用已删除程序集提供的功能,请引用对应包的 3.0 版本:

启动更改

下图显示了 ASP.NET Core 2.2 Razor Pages Web 应用中删除和更改的行:

ASP.NET Core 2.2 Razor Web 应用中已删除和更改的行

在上图中,删除的代码显示为红色。 删除的代码未显示 cookie 选项代码,该代码在比较文件之前已删除。

下图显示了 ASP.NET Core 3.0 Razor Pages Web 应用中添加和更改的行:

ASP.NET Core 3.0 Razor Web 应用中已添加和更改的行

在上图中,添加的代码显示为绿色。 有关以下更改的信息:

  • services.AddMvcservices.AddRazorPages,请参阅本文档中的 MVC 服务注册
  • CompatibilityVersion请参阅 ASP.NET Core MVC 的兼容性版本
  • IHostingEnvironmentIWebHostEnvironment,请参阅此 GitHub 公告
  • app.UseAuthorization 已添加到模板,以显示必须添加订单授权中间件。 如果应用不使用授权,则可以安全地删除对 app.UseAuthorization 的调用。
  • app.UseEndpoints,请参阅Razor本文档中的 Pages 或 Migrate Startup.Configure

分析器支持

面向 Microsoft.NET.Sdk.Web 的项目会隐式引用以前作为 Microsoft.AspNetCore.Mvc.Analyzers 包的一部分提供的分析器。 无需其他引用即可启用这些内容。

如果应用使用以前通过 Microsoft.AspNetCore.Mvc.Api.Analyzers 包提供的 API 分析器,请编辑项目文件以引用作为 .NET Core Web SDK 的一部分提供的分析器:

<Project Sdk="Microsoft.NET.Sdk.Web">
    <PropertyGroup>
        <TargetFramework>netcoreapp3.0</TargetFramework>
        <IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
    </PropertyGroup>

    ...
</Project>

Razor 类库

为 MVC 提供 UI 组件的 Razor 类库项目必须在项目文件中设置 AddRazorSupportForMvc 属性:

<PropertyGroup>
  <AddRazorSupportForMvc>true</AddRazorSupportForMvc>
</PropertyGroup>

进程内托管模型

在 ASP.NET Core 3.0 或更高版本中,项目默认为进程内托管模型。 如果值为 InProcess,可以选择在项目文件中删除 <AspNetCoreHostingModel> 属性。

Kestrel

Configuration

将配置迁移到Kestrel (Program.cs) 提供的 ConfigureWebHostDefaultsWeb 主机生成器

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(serverOptions =>
            {
                // Set properties and call methods on options
            })
            .UseStartup<Startup>();
        });

如果应用使用 ConfigureWebHost 而不是 ConfigureWebHostDefaults 手动创建主机,请对 Web 主机生成器调用 UseKestrel

public static void Main(string[] args)
{
    var host = new HostBuilder()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .ConfigureWebHost(webBuilder =>
        {
            webBuilder.UseKestrel(serverOptions =>
            {
                // Set properties and call methods on options
            })
            .UseIISIntegration()
            .UseStartup<Startup>();
        })
        .Build();

    host.Run();
}

连接中间件替换连接适配器

连接适配器 (Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal.IConnectionAdapter) 已从 Kestrel 中删除。 将连接适配器替换为连接中间件。 连接中间件类似于 ASP.NET Core 管道中的 HTTP 中间件,但适用于较低级别的连接。 HTTPS 和连接日志记录:

  • 已从连接适配器移动到连接中间件。
  • 这些扩展方法的工作方式与以前版本的 ASP.NET Core 中相同。

有关详细信息,请参阅 本文的 ListenOptions.Protocols 部分中的 Kestrel TlsFilterConnectionHandler 示例

移动并公开的传输抽象

Kestrel 传输层已作为 Connections.Abstractions 中的公共接口公开。 作为这些更新的一部分:

  • Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions 和关联类型已删除。
  • NoDelay 已从 ListenOptions 移动到传输选项。
  • Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal.SchedulingMode 已从 KestrelServerOptions 中删除。

有关详细信息,请参阅以下 GitHub 资源:

Kestrel 请求尾部标头

对于面向早期版本 ASP.NET Core 的应用:

  • Kestrel 会将 HTTP/1.1 分块尾部标头添加到请求头集合中。
  • 在读取请求正文直到结尾后,可使用尾部。

这会导致一些有关标头与尾部之间的多义性的问题,因此在 3.0 中,尾端已移动到新集合 (RequestTrailerExtensions)。

HTTP/2 请求尾端:

  • 在 ASP.NET Core 2.2 中不可用。
  • 在 3.0 中作为 RequestTrailerExtensions 提供。

存在新的请求扩展方法以访问这些尾部。 与 HTTP/1.1 一样,在读取请求正文直到结尾后,可使用尾部。

对于 3.0 版,可使用以下 RequestTrailerExtensions 方法:

  • GetDeclaredTrailers:获取列出了正文后应具有的尾部的请求 Trailer 标头。
  • SupportsTrailers:指示请求是否支持接收尾部标头。
  • CheckTrailersAvailable:检查请求是否支持尾部以及是否可读取。 此检查不假定存在要读取的尾端。 即使此方法返回 true,也可能没有要读取的尾端。
  • GetTrailer:从响应获取请求的尾随标头。 请在调用 GetTrailer 之前检查 SupportsTrailers,否则如果请求不支持尾随标头,则可能会发生 NotSupportedException

有关详细信息,请参阅将请求尾部置于单独集合中 (dotnet/AspNetCore #10410)

已禁用 AllowSynchronousIO

AllowSynchronousIO 可启用或禁用同步 I/O API,例如 HttpRequest.Body.ReadHttpResponse.Body.WriteStream.Flush。 这些 API 是导致应用崩溃的线程不足的原因。 在 3.0 中,默认情况下禁用 AllowSynchronousIO。 有关详细信息,请参阅 本文中的 Kestrel 同步 I/O 部分

如果需要同步 I/O,可以通过在所使用的服务器上配置 AllowSynchronousIO 选项(例如如果使用 Kestrel则在调用 ConfigureKestrel 时)来启用它。 请注意,服务器 (Kestrel、HttpSys、TestServer 等) 都有自己的 AllowSynchronousIO 选项,不会影响其他服务器。 可以使用 IHttpBodyControlFeature.AllowSynchronousIO 选项,基于请求为所有服务器启用同步 I/O:

var syncIOFeature = HttpContext.Features.Get<IHttpBodyControlFeature>();

if (syncIOFeature != null)
{
    syncIOFeature.AllowSynchronousIO = true;
}

如果调用 Dispose 中同步 API 的 TextWriter 实现或其他流出现问题,请改为调用新的 DisposeAsync API。

有关详细信息,请参阅 [公告] AllowSynchronousIO 在所有服务器中已禁用 (dotnet/AspNetCore #7644)

输出格式化程序缓冲

基于 Newtonsoft.JsonXmlSerializerDataContractSerializer 输出格式化程序仅支持同步序列化。 为了使这些格式化程序可以处理服务器的 AllowSynchronousIO 限制,MVC 会在向磁盘写入之前缓冲这些格式化程序的输出。 作为缓冲的结果,当使用这些格式化程序响应时,MVC 会包含 Content-Length 标头。

System.Text.Json 支持异步序列化,因此基于 System.Text.Json 的格式化程序不会缓冲。 请考虑使用此格式化程序提高性能。

若要禁用缓冲,应用程序可以在启动中配置 SuppressOutputFormatterBuffering

services.AddControllers(options => options.SuppressOutputFormatterBuffering = true)

请注意,如果还未配置 AllowSynchronousIO,则这可能会导致应用程序引发运行时异常。

Microsoft.AspNetCore.Server.Kestrel.Https 程序集已删除

在 ASP.NET Core 2.1 中,Microsoft.AspNetCore.ServerKestrel 的内容。Https.dll已移动到 Microsoft.AspNetCore.Server。KestrelCore.dll. 这是使用 TypeForwardedTo 属性的非中断性更新。 对于 3.0,空的 Microsoft.AspNetCore.Server。Kestrel。Https.dll 程序集和 NuGet 包已删除。

引用 Microsoft.AspNetCore.Server.Kestrel的库。Https 应将 ASP.NET Core依赖项更新到 2.1 或更高版本。

面向 ASP.NET Core 2.1 或更高版本的应用和库应删除对 Microsoft.AspNetCore.ServerKestrel 的任何直接引用。Https 包。

Newtonsoft.Json (Json.NET) 支持

作为改进 ASP.NET Core 共享框架的工作的一部分,Newtonsoft.Json (Json.NET) 已从 ASP.NET Core 共享框架中删除。

ASP.NET Core的默认 JSON 序列化程序现在是 System.Text.Json.NET Core 3.0 中的新增功能。 如果可能,请考虑使用 System.Text.Json。 它是高性能的,不需要额外的库依赖项。 不过,由于 System.Text.Json 是新功能,它当前可能缺少应用所需的功能。 有关详细信息,请参阅如何从 Newtonsoft.Json 迁移到 System.Text.Json

在 ASP.NET Core 3.0 SignalR 项目中使用 Newtonsoft.Json

  • 安装 Microsoft.AspNetCore.SignalR。Protocols.NewtonsoftJson NuGet 包。

  • 在客户端上,将 AddNewtonsoftJsonProtocol 方法调用链接到 HubConnectionBuilder 实例:

    new HubConnectionBuilder()
        .WithUrl("/chathub")
        .AddNewtonsoftJsonProtocol(...)
        .Build();
    
  • 在服务器上,将 AddNewtonsoftJsonProtocol 方法调用链接到 Startup.ConfigureServices 中的 AddSignalR 方法调用:

    services.AddSignalR()
        .AddNewtonsoftJsonProtocol(...);
    

在 ASP.NET Core 3.0 MVC 项目中使用 Newtonsoft.Json

  • 安装 Microsoft.AspNetCore.Mvc.NewtonsoftJson 包。

  • 更新 Startup.ConfigureServices 以调用 AddNewtonsoftJson

    services.AddMvc()
        .AddNewtonsoftJson();
    

    AddNewtonsoftJson 与新的 MVC 服务注册方法兼容:

    • AddRazorPages
    • AddControllersWithViews
    • AddControllers
    services.AddControllers()
        .AddNewtonsoftJson();
    

    Newtonsoft.Json 设置可以在对 AddNewtonsoftJson 的调用中进行设置:

    services.AddMvc()
        .AddNewtonsoftJson(options =>
               options.SerializerSettings.ContractResolver =
                  new CamelCasePropertyNamesContractResolver());
    

    注意:如果 AddNewtonsoftJson 方法不可用,请确保安装了 Microsoft.AspNetCore.Mvc.NewtonsoftJson 包。 一种常见错误是安装 Newtonsoft.json 包而不是 Microsoft.AspNetCore.Mvc.NewtonsoftJson 包。

有关详细信息,请参阅 添加基于 Newtonsoft.Json 的 ON JS格式支持

MVC 服务注册

ASP.NET Core 3.0 添加了用于在 Startup.ConfigureServices 中注册 MVC 方案的新选项。

提供了三种与 IServiceCollection 中的 MVC 方案相关的顶级扩展方法。 模板使用这些新方法而不是 AddMvc。 但是,AddMvc 会继续保持以前版本中的行为方式。

下面的示例添加了对控制器和 API 相关功能的支持,但不支持视图或页面。 API 模板使用以下代码:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
}

下面的示例添加了对控制器、API 相关功能和视图的支持,但不支持页面。 Web 应用 (MVC) 模板使用以下代码:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
}

下面的示例添加了对 Razor Pages 的支持和最小控制器支持。 Web 应用模板使用以下代码:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
}

新方法还可以合并。 下面的示例等效于在 ASP.NET Core 2.2 中调用 AddMvc

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
}

路由启动代码

如果应用调用 UseMvcUseSignalR,请将应用迁移到终结点路由(如果可能)。 为了改进与以前版本的 MVC 的终结点路由兼容性,我们还原了 ASP.NET Core 2.2 引入的 URL 生成中的一些更改。 如果在 2.2 中使用终结点路由时遇到问题,请期待 ASP.NET Core 3.0 中的改进,不过有以下例外:

  • 如果应用实现 IRouter 或继承自 Route ,请使用 DynamicRouteValuesTransformer 作为替换。
  • 如果应用在 MVC 内直接访问 RouteData.Routers 以分析 URL,则可以将此内容替换为使用 LinkParser.ParsePathByEndpointName
    • 使用路由名称定义路由。
    • 使用 LinkParser.ParsePathByEndpointName 并传入所需的路由名称。

终结点路由支持与 IRouter 相同的路由模式语法和路由模式创作功能。 终结点路由支持 IRouteConstraint。 终结点路由支持 [Route][HttpGet] 和其他 MVC 路由属性。

对于大多数应用程序,只有 Startup 需要更改。

迁移 Startup.Configure

一般建议:

  • 添加 UseRouting

  • 如果应用调用UseStaticFiles请放在UseStaticFiles前面UseRouting

  • 如果应用使用身份验证/授权功能(例如AuthorizePage,或[Authorize])将调用UseAuthenticationUseAuthorization置于以下位置:之后UseRoutingUseCors但之前:UseEndpoints

    public void Configure(IApplicationBuilder app)
    {
      ...
    
      app.UseStaticFiles();
    
      app.UseRouting();
      app.UseCors();
    
      app.UseAuthentication();
      app.UseAuthorization();
    
      app.UseEndpoints(endpoints => {
         endpoints.MapControllers();
      });
    
  • UseMvcUseSignalR 替换为 UseEndpoints

  • 如果应用使用 CORS 方案(如 [EnableCors]),请将对 UseCors 的调用放置在使用 CORS 的任何其他中间件前面(例如,将 UseCors 放置在 UseAuthenticationUseAuthorizationUseEndpoints 前面)。

  • IHostingEnvironment 替换为 IWebHostEnvironment,并为 Microsoft.AspNetCore.Hosting 命名空间添加 using 语句。

  • IApplicationLifetime 替换为 IHostApplicationLifetimeMicrosoft.Extensions.Hosting 命名空间)。

  • EnvironmentName 替换为 EnvironmentsMicrosoft.Extensions.Hosting 命名空间)。

下面的代码是典型 ASP.NET Core 2.2 应用中的 Startup.Configure 的示例:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseStaticFiles();

    app.UseAuthentication();

    app.UseSignalR(hubs =>
    {
        hubs.MapHub<ChatHub>("/chat");
    });

    app.UseMvc(routes =>
    {
        routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
    });
}

更新上面的 Startup.Configure 代码后:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseStaticFiles();

    app.UseRouting();

    app.UseCors();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHub<ChatHub>("/chat");
        endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
    });
}

警告

对于大多数应用,对 UseAuthenticationUseAuthorizationUseCors 的调用必须出现在对 UseRoutingUseEndpoints 的调用之间才能生效。

运行状况检查

运行状况检查将终结点路由与泛型主机一起使用。 在 Startup.Configure 内,使用终结点 URL 或相对路径在终结点生成器上调用 MapHealthChecks

app.UseEndpoints(endpoints =>
{
    endpoints.MapHealthChecks("/health");
});

运行状况检查终结点可以:

  • 指定一个或多个允许的主机/端口。
  • 需要授权。
  • 需要 CORS。

有关详细信息,请参阅 ASP.NET Core 中的运行状况检查

安全性中间件指南

对授权和 CORS 的支持围绕中间件方法统一。 这样便可在这些方案中使用相同的中间件和功能。 此版本提供了更新的授权中间件,并增强了 CORS 中间件,使它可以理解 MVC 控制器使用的属性。

CORS

以前,CORS 可能难以配置。 提供中间件是为了在某些用例中使用,而 MVC 筛选器是为了在没有中间件的情况下,在其他用例中使用。 对于 ASP.NET Core 3.0,建议所有需要 CORS 的应用都将 CORS 中间件与终结点路由结合使用。 UseCors 可以通过默认策略提供,而 [EnableCors][DisableCors] 属性可用于在需要时替代默认策略。

在以下示例中:

  • CORS 对于具有 default 命名策略的所有终结点都处于启用状态。
  • MyController 类通过 [DisableCors] 属性禁用 CORS。
public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseCors("default");

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

[DisableCors]
public class MyController : ControllerBase
{
    ...
}

授权

在早期版本的 ASP.NET Core 中,通过 [Authorize] 属性提供授权支持。 授权中间件不可用。 在 ASP.NET Core 3.0 中,授权中间件是必需的。 建议 ASP.NET Core 授权中间件 (UseAuthorization) 的放置位置是紧跟在 UseAuthentication 后面。 授权中间件也可以使用可替代的默认策略进行配置。

在 ASP.NET Core 3.0 或更高版本中,UseAuthorizationStartup.Configure 中进行调用,下面的 HomeController 需要已登录用户:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

public class HomeController : Controller
{
    [Authorize]
    public IActionResult BuyWidgets()
    {
        ...
    }
}

使用终结点路由时,建议不要配置 AuthorizeFilter,而是依赖授权中间件。 如果应用在 MVC 中使用 AuthorizeFilter 作为全局筛选器,则建议重构代码,以便在对 AddAuthorization 的调用中提供策略。

DefaultPolicy 在开始时便配置为需要身份验证,因此无需进行其他配置。 在下面的示例中,MVC 终结点标记为 RequireAuthorization,以便所有请求都必须基于 DefaultPolicy 进行授权。 但是由于 [AllowAnonymous]HomeController 允许在用户未登录应用的情况下进行访问:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute().RequireAuthorization();
    });
}

[AllowAnonymous]
public class HomeController : Controller
{
    ...
}

对特定终结点的授权

也可以为特定的终结点类配置授权。 下面的代码示例将配置可全局 AuthorizeFilter 的 MVC 应用转换具有需要授权的特定策略的应用:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    static readonly string _RequireAuthenticatedUserPolicy = 
                            "RequireAuthenticatedUserPolicy";
    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
        services.AddDefaultIdentity<IdentityUser>(
                 options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();

        // Pre 3.0:
        // services.AddMvc(options => options.Filters.Add(new AuthorizeFilter(...));

        services.AddControllersWithViews();
        services.AddRazorPages();
        services.AddAuthorization(o => o.AddPolicy(_RequireAuthenticatedUserPolicy,
                        builder => builder.RequireAuthenticatedUser()));

    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }
        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapDefaultControllerRoute()
                .RequireAuthorization(_RequireAuthenticatedUserPolicy);
            endpoints.MapRazorPages();
        });
    }
}

也可以自定义策略。 DefaultPolicy 配置为需要身份验证:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
        services.AddDefaultIdentity<IdentityUser>(
                 options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddControllersWithViews();
        services.AddRazorPages();
        services.AddAuthorization(options =>
        {
            options.DefaultPolicy = new AuthorizationPolicyBuilder()
              .RequireAuthenticatedUser()
              .Build();
        });

    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }
        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapDefaultControllerRoute().RequireAuthorization();
            endpoints.MapRazorPages();
        });
    }
}
[AllowAnonymous]
public class HomeController : Controller
{

或者,可以通过配置 FallbackPolicy,将所有终结点配置为在没有 [Authorize]RequireAuthorization 的情况下要求授权。 FallbackPolicyDefaultPolicy 不同。 DefaultPolicy[Authorize]RequireAuthorization 触发,而 FallbackPolicy 在未设置其他策略时触发。 FallbackPolicy 最初配置为允许未经授权进行请求。

下面的示例与上面的 DefaultPolicy 示例相同,但使用 FallbackPolicy 以便在所有终结点上始终需要身份验证(在指定 [AllowAnonymous] 时除外):

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddAuthorization(options =>
    {
        options.FallbackPolicy = new AuthorizationPolicyBuilder()
          .RequireAuthenticatedUser()
          .Build();
    });
}

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });
}

[AllowAnonymous]
public class HomeController : Controller
{
    ...
}

中间件的授权可在框架不了解授权的任何特定信息的情况下正常工作。 例如,运行状况检查不了解授权的特定信息,但运行状况检查可以具有由中间件应用的可配置授权策略。

此外,每个终结点都可以自定义其授权要求。 在下面的示例中,UseAuthorization 使用 DefaultPolicy 处理授权,但是 /healthz 运行状况检查终结点需要 admin 用户:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints
            .MapHealthChecks("/healthz")
            .RequireAuthorization(new AuthorizeAttribute(){ Roles = "admin", });
    });
}

对于某些方案实现了保护。 如果由于缺少中间件而跳过授权或 CORS 策略,则终结点中间件会引发异常。 提供有关配置错误的其他反馈的分析器支持正在准备中。

自定义授权处理程序

如果应用使用自定义授权处理程序,则终结点路由会将与 MVC 不同的资源类型传递给处理程序。 需要类型为 AuthorizationFilterContextMVC 筛选器提供的资源类型)的授权处理程序上下文资源的处理程序需要更新,以处理类型为 RouteEndpoint(终结点路由向授权处理程序提供的资源类型)的资源。

MVC 仍使用 AuthorizationFilterContext 资源,因此,如果应用使用 MVC 授权筛选器以及终结点路由授权,则可能需要处理这两种类型的资源。

SignalR

现在,SignalR 中心的映射在 UseEndpoints 中进行。

将每个中心与 MapHub 映射。 与以前的版本一样,每个中心都会显式列出。

在下面的示例中,添加了对 ChatHubSignalR 中心的支持:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHub<ChatHub>();
    });
}

有一个新选项可用于从客户端控制消息大小限制。 例如,在 Startup.ConfigureServices 中:

services.AddSignalR(hubOptions =>
{
    hubOptions.MaximumReceiveMessageSize = 32768;
});

在 ASP.NET Core 2.2 中,可以设置 TransportMaxBufferSize,这会有效控制最大消息大小。 在 ASP.NET Core 3.0 中,该选项现在仅控制观察到背压之前的最大大小。

MVC 控制器

现在,控制器的映射在 UseEndpoints 中进行。

如果应用使用属性路由,则添加 MapControllers。 由于路由包含对 ASP.NET Core 3.0 或更高版本中许多框架的支持,因此可以选择添加属性路由控制器。

  • MapControllerRouteMapRoute
  • MapAreaControllerRouteMapAreaRoute

由于路由现在包含 MVC 支持以外的支持,因此术语进行了更改,使这些方法可清楚地声明其作用。 传统路由(例如 MapControllerRoute/MapAreaControllerRoute/MapDefaultControllerRoute)按照其添加顺序进行应用。 首先放置更特定的路由(如某个区域的路由)。

在以下示例中:

  • MapControllers 添加对属性路由控制器的支持。
  • MapAreaControllerRoute 为某个区域中的控制器添加传统路由。
  • MapControllerRoute 为控制器添加传统路由。
public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapAreaControllerRoute(
            "admin",
            "admin",
            "Admin/{controller=Home}/{action=Index}/{id?}");
        endpoints.MapControllerRoute(
            "default", "{controller=Home}/{action=Index}/{id?}");
    });
}

控制器操作名称中的 Async 后缀删除

在 ASP.NET Core 3.0 中,ASP.NET Core MVC 会从控制器操作名称中删除 Async 后缀。 此新默认设置会影响路由和链接生成。 例如:

public class ProductsController : Controller
{
    public async Task<IActionResult> ListAsync()
    {
        var model = await _dbContext.Products.ToListAsync();
        return View(model);
    }
}

在 ASP.NET Core 3.0 之前:

  • 可以在 Products/ListAsync 路由上访问以上操作。

  • 链接生成需要指定 Async 后缀。 例如:

    <a asp-controller="Products" asp-action="ListAsync">List</a>
    

在 ASP.NET Core 3.0 中:

  • 可以在 Products/List 路由上访问以上操作。

  • 链接生成无需指定 Async 后缀。 例如:

    <a asp-controller="Products" asp-action="List">List</a>
    

此更改不会影响使用 [ActionName] 属性指定的名称。 可以在 Startup.ConfigureServices 中通过以下代码禁用默认行为:

services.AddMvc(options =>
    options.SuppressAsyncSuffixInActionNames = false);

链接生成中存在一些差异(例如使用 Url.Link 和相似 API)。 其中包括:

  • 默认情况下,使用终结点路由时,不一定要保留生成的 URI 中路由参数的大小写。 此行为可通过 IOutboundParameterTransformer 接口进行控制。
  • 为无效路由(不存在的控制器/操作或页面)生成 URI 会在终结点路由下生成空字符串,而不是生成无效 URI。
  • 环境值(当前上下文中的路由参数)不会自动用于通过终结点路由进行的链接生成。 以前在生成指向其他操作(或页面)的链接时,会从当前路由环境值推断未指定的路由值。 使用终结点路由时,必须在生成链接期间显式指定所有路由参数。

Razor Pages

现在,Razor Pages 映射在 UseEndpoints 中进行。

如果应用使用 Razor Pages,则添加 MapRazorPages。 由于终结点路由包含对许多框架的支持,因此现在可以选择添加 Razor Pages。

在下面的 Startup.Configure 方法中,MapRazorPages 添加对 Razor Pages 的支持:

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

在没有终结点路由的情况下使用 MVC

在 ASP.NET Core 3.0 中通过 UseMvcUseMvcWithDefaultRoute 使用 MVC 需要在 Startup.ConfigureServices 中进行显式选择。 这是必需操作,因为 MVC 必须知道它是否可以在初始化期间依赖于授权和 CORS 中间件。 提供了一个分析器,以便在应用尝试使用不支持的配置时发出警告。

如果应用需要旧的 IRouter 支持,请在 Startup.ConfigureServices 中使用以下任何方法禁用 EnableEndpointRouting

services.AddMvc(options => options.EnableEndpointRouting = false);
services.AddControllers(options => options.EnableEndpointRouting = false);
services.AddControllersWithViews(options => options.EnableEndpointRouting = false);
services.AddRazorPages().AddMvcOptions(options => options.EnableEndpointRouting = false);

运行状况检查

运行状况检查可以在具有终结点路由的情况下用作路由器感知。

添加 MapHealthChecks 以将运行状况检查与终结点路由一起使用。 MapHealthChecks 方法接受与 UseHealthChecks 相似的参数。 使用 MapHealthChecks 相对于 UseHealthChecks 的优点是能够应用授权,并且可更精细地控制匹配策略。

在下面的示例中,会为 /healthz 处的运行状况检查终结点调用 MapHealthChecks

public void Configure(IApplicationBuilder app)
{
    ...

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHealthChecks("/healthz", new HealthCheckOptions() { });
    });
}

HostBuilder 替换 WebHostBuilder

ASP.NET Core 3.0 模板使用通用主机。 以前版本使用 Web 主机。 下面的代码演示 ASP.NET Core 3.0 模板生成的 Program 类:

// requires using Microsoft.AspNetCore.Hosting;
// requires using Microsoft.Extensions.Hosting;

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

下面的代码演示 ASP.NET Core 2.2 模板生成的 Program 类:

public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

IWebHostBuilder 在 3.0 中保留,是上面代码示例中所示的 webBuilder 的类型。 WebHostBuilder 将在未来版本中弃用,并替换为 HostBuilder

WebHostBuilderHostBuilder 的最显著更改在依赖项注入 (DI) 中。 使用 HostBuilder 时,只能将以下内容注入 Startup 的构造函数中:

HostBuilder DI 约束:

  • 使 DI 容器可以仅生成一次。
  • 避免导致对象生存期问题,例如解析单一实例的多个实例。

有关详细信息,请参阅在 ASP.NET Core 3 中避免启动服务注入

AddAuthorization 已移到其他程序集

Microsoft.AspNetCore.Authorization.dll中的 ASP.NET Core 2.2 和更低AddAuthorization方法:

  • 已重命名为 AddAuthorizationCore
  • 已移动到 Microsoft.AspNetCore.Authorization.Policy.dll。

同时使用 Microsoft.AspNetCore.Authorization.dll 和 Microsoft.AspNetCore.Authorization.Policy.dll 的应用不受影响。

未使用 Microsoft.AspNetCore.Authorization.Policy.dll 的应用应执行以下操作之一:

  • 添加对 Microsoft.AspNetCore.Authorization.Policy.dll 的引用。 此方法适用于大多数应用,并且是唯一的必需操作。
  • 切换到使用 AddAuthorizationCore

有关详细信息,请参阅 AddAuthorization(o =>) 重载中的中断性变更处于其他程序集 #386

Identity UI

针对 ASP.NET Core 3.0 的 Identity UI 更新:

SignalR

SignalR JavaScript 客户端从 @aspnet/signalr 更改为 @microsoft/signalr。 若要对此更改做出响应,请更改文件、require语句和 ECMAScript import 语句中的package.json引用。

System.Text.Json 是默认协议

System.Text.Json 现在是客户端和服务器使用的默认中心协议。

Startup.ConfigureServices 中,调用 AddJsonProtocol 以设置序列化程序选项。

服务器:

services.AddSignalR(...)
        .AddJsonProtocol(options =>
        {
            options.PayloadSerializerOptions.WriteIndented = false;
        })

客户端:

new HubConnectionBuilder()
    .WithUrl("/chathub")
    .AddJsonProtocol(options =>
    {
        options.PayloadSerializerOptions.WriteIndented = false;
    })
    .Build();

切换到 Newtonsoft.Json

如果使用 中不支持的 Newtonsoft.Json 功能,则可以切换回 Newtonsoft.Json。 请参阅本文前面的 ASP.NET Core 3.0 SignalR 项目中的 Newtonsoft.Json

Redis 分布式缓存

包不适用于 ASP.NET Core 3.0 或更高版本应用。 将包引用替换为 Microsoft.Extensions.Caching.StackExchangeRedis。 有关详细信息,请参阅 ASP.NET Core 中的分布式缓存

选择进行运行时编译

在 ASP.NET Core 3.0 之前,视图的运行时编译是框架的隐式功能。 运行时编译可对视图的生成时编译进行补充。 它允许框架在 Razor 修改文件时) .cshtml (文件编译视图和页面,而无需重新生成整个应用。 此功能支持在 IDE 中进行快速编辑并刷新浏览器以查看更改的方案。

在 ASP.NET Core 3.0 中,运行时编译是一种选择加入方案。 生成时编译是适用于视图编译的默认启用的的唯一机制。 运行时依赖于 Visual Studio Code中的 Visual Studio 或 dotnet-watch,在项目检测到文件更改.cshtml时重新生成项目。 在 Visual Studio 中, (Ctrl+F5) 运行项目中的更改.cs.cshtml.razor文件,但未调试 (F5) ,触发项目的重新编译。

若要在 ASP.NET Core 3.0 项目中启用运行时编译,请执行以下操作:

  1. 安装 Microsoft.AspNetCore.Mvc.Razor。RuntimeCompilation NuGet 包。

  2. 更新 Startup.ConfigureServices 以调用 AddRazorRuntimeCompilation

    对于 ASP.NET Core MVC,请使用以下代码:

    services.AddControllersWithViews()
        .AddRazorRuntimeCompilation(...);
    

    对于 ASP.NET Core Razor Pages,请使用以下代码:

    services.AddRazorPages()
        .AddRazorRuntimeCompilation(...);
    

https://github.com/aspnet/samples/tree/main/samples/aspnetcore/mvc/runtimecompilation 上的示例演示了在开发环境中有条件地启用运行时编译的示例。

有关文件编译的详细信息Razor,请参阅 Razor ASP.NET Core中的文件编译

通过多目标迁移库

库通常需要支持多个版本的 ASP.NET Core。 针对以前版本的 ASP.NET Core 编译的大多数库应该可以继续正常工作,不会出现问题。 以下状况要求对应用进行交叉编译:

  • 库依赖于具有二进制文件中断性变更的功能。
  • 库希望利用 ASP.NET Core 3.0 中的新功能。

例如:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netcoreapp3.0;netstandard2.0</TargetFrameworks>
  </PropertyGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
    <PackageReference Include="Microsoft.AspNetCore" Version="2.1.0" />
  </ItemGroup>
</Project>

使用 #ifdefs 启用特定于 ASP.NET Core 3.0 的 API:

var webRootFileProvider =
#if NETCOREAPP3_0
    GetRequiredService<IWebHostEnvironment>().WebRootFileProvider;
#elif NETSTANDARD2_0
    GetRequiredService<IHostingEnvironment>().WebRootFileProvider;
#else
#error unknown target framework
#endif

有关在类库中使用 ASP.NET Core API 的详细信息,请参阅在类库中使用 ASP.NET Core API

其他更改

.NET Core 3.0 和更高版本中的验证系统将不可为 null 的参数或绑定属性视为具有 [Required] 特性。 有关详细信息,请参阅 [Required] 属性

发布

在项目目录中删除 bin 和 obj 文件夹。

TestServer

对于不直接通过通用主机使用 TestServer 的应用,请在 ConfigureWebHost 中对 IWebHostBuilder 创建 TestServer

[Fact]
public async Task GenericCreateAndStartHost_GetTestServer()
{
    using var host = await new HostBuilder()
        .ConfigureWebHost(webBuilder =>
        {
            webBuilder
                .UseTestServer()
                .Configure(app => { });
        })
    .StartAsync();

    var response = await host.GetTestServer().CreateClient().GetAsync("/");

    Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

中断性 API 变更

查看中断性变更:

使用 catch-all 参数的终结点路由

警告

由于路由中的 bugcatch-all 参数可能无法正确匹配相应路由。 受此 Bug 影响的应用具有以下特征:

  • “全部捕获”路由,例如 {**slug}"
  • “全部捕获”路由未能匹配应与之匹配的请求。
  • 删除其他路由可使“全部捕获”路由开始运行。

请参阅 GitHub bug 1867716579,了解遇到此 bug 的示例。

.NET Core 3.1.301 SDK 及更高版本中包含此 bug 的修补程序(可选用)。 以下代码设置了一个可修复此 bug 的内部开关:

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

Azure 应用服务中的 .NET Core 3.0

.NET Core 到 Azure 应用服务的推出已完成。 所有 Azure 应用服务数据中心中都提供了 .NET Core 3.0。

ASP.NET CORE模块 (ANCM)

如果安装 Visual Studio 时未选择 ASP.NET CORE模块 (ANCM) 或系统上安装了早期版本的 ANCM,请下载最新的 .NET Core 托管捆绑包安装程序, (直接下载) 并运行安装程序。 有关详细信息,请参阅 托管捆绑包