从 ASP.NET Core 5.0 迁移到 6.0

本文介绍如何将现有 ASP.NET Core 5.0 项目更新为 ASP.NET Core 6.0。 有关如何从 ASP.NET Core 3.1 迁移到 ASP.NET Core 6.0 的说明,请参阅从 ASP.NET Core 3.1 迁移到 6.0

先决条件

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

如果依靠 global.json 文件来面向特定 .NET SDK 版本,请将 version 属性更新为已安装的 .NET 6.0 SDK 版本。 例如:

{
  "sdk": {
-    "version": "5.0.100"
+    "version": "6.0.100"
  }
}

更新目标框架

将项目文件的目标框架名字对象 (TFM) 更新为 net6.0

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

  <PropertyGroup>
-    <TargetFramework>net5.0</TargetFramework>
+    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

</Project>

更新包引用

在项目文件中,将每个 Microsoft.AspNetCore.*Microsoft.Extensions.* 包引用的 Version 特性更新为 6.0.0 或更高版本。 例如:

<ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="5.0.3" />
-    <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" />
+    <PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="6.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
</ItemGroup>

新托管模型

适用于 ASP.NET Core 的新的 .NET 6 最小托管模型只需要一个文件和几行代码。 迁移到 6.0 的应用不需要使用新的最小托管模型。 有关详细信息,请参阅以下部分中的迁移到 6.0 的应用不需要使用新的最小托管模型

ASP.NET Core 空模板中的以下代码使用新的最小托管模型创建应用:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

最小托管模型:

  • 显著减少创建应用所需的文件和代码行数。 只需要一个文件和四行代码。
  • Startup.csProgram.cs 统一到单个 Program.cs 文件中。
  • 使用顶级语句最大程度减少应用所需的代码。
  • 使用全局 指令消除或最大程度减少所需的 语句行数。

以下代码显示 ASP.NET Core 5 Web 应用模板 (Razor Pages) 中的 Startup.csProgram.cs 文件,其中删除了未使用的 using 语句:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
// Unused usings removed.

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

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
            });
        }
    }
}
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
// Unused usings removed.

namespace WebAppRPv5
{
    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 6 中,上面的代码替换为以下内容:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

上面的 ASP.NET Core 6 示例演示了以下操作方式:

本文档后面会提供使用最小托管模型将 ASP.NET Core 5 Startup 代码迁移到 ASP.NET Core 6 的详细示例。

对为 Web 应用模板生成的其他文件进行了几处更改:

  • Index.cshtmlPrivacy.cshtml 删除了未使用的 using 语句。
  • Error.cshtml 中的 RequestId 声明为空引用类型 (NRT)
- public string RequestId { get; set; }
+ public string? RequestId { get; set; }
  • 日志级别默认值已更改, appsettings.json 并且 appsettings.Development.json
- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
+ "Microsoft.AspNetCore": "Warning"

在前面的 ASP.NET Core模板代码中,"Microsoft": "Warning"已更改为 "Microsoft.AspNetCore": "Warning"。 此更改会导致记录 Microsoft 命名空间中的所有信息性消息, 但除外Microsoft.AspNetCore。 例如,Microsoft.EntityFrameworkCore 现记录在信息级别。

有关新托管模型的更多详细信息,请参阅常见问题解答部分。 有关采用 NRT 和 .NET 编译器 Null 状态分析的详细信息,请参阅空引用类型 (NRT) 和 .NET 编译器 Null 状态静态分析部分。

迁移到 6.0 的应用不需要使用新的最小托管模型

完全支持使用 Startup 以及由 ASP.NET Core 3.1 和 5.0 模板使用的通用主机

将 Startup 与新的最小托管模型结合使用

ASP.NET Core 3.1 和 5.0 应用可以将其 Startup 代码与新的最小托管模型结合使用。 请考虑由 ASP.NET Core 3.1 或 5.0 Razor Pages 模板生成的代码:

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>();
            });
}
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

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

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

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

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

上面的代码已迁移到新的最小托管模型:

using Microsoft.AspNetCore.Builder;

var builder = WebApplication.CreateBuilder(args);

var startup = new Startup(builder.Configuration);

startup.ConfigureServices(builder.Services);

var app = builder.Build();

startup.Configure(app, app.Environment);

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

    public IConfiguration Configuration { get; }

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

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (!env.IsDevelopment())
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

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

在上面的代码中,删除了 if (env.IsDevelopment()) 程序块,因为在开发模式下,开发人员异常页中间件在默认情况下处于启用状态。 有关详细信息,请参阅下一部分中的 ASP.NET Core 5 与 6 托管模型之间的差异

使用自定义依赖项注入 (DI) 容器时,请添加以下突出显示的代码:

using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);

var startup = new Startup(builder.Configuration);

startup.ConfigureServices(builder.Services);

// Using a custom DI container.
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(startup.ConfigureContainer);

var app = builder.Build();

startup.Configure(app, app.Environment);

app.Run();
using Autofac;
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

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

    //  Using a custom DI container
    public void ConfigureContainer(ContainerBuilder builder)
    {
        // Configure custom container.
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (!env.IsDevelopment())
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

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

使用最小托管模型时,终结点路由中间件会包装整个中间件管道,因此无需显式调用 UseRoutingUseEndpoints 来注册路由。 UseRouting 仍可用于指定进行路由匹配的位置,但如果应在中间件管道开头匹配路由,则无需显式调用 UseRouting

在下面的代码中,从 Startup 中删除了对 UseRoutingUseEndpoints 的调用。 MapRazorPagesProgram.cs在 :

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

    public IConfiguration Configuration { get; }

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

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (!env.IsDevelopment())
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        //app.UseRouting();

        //app.UseEndpoints(endpoints =>
        //{
        //    endpoints.MapRazorPages();
        //});
    }
}
using Microsoft.AspNetCore.Builder;

var builder = WebApplication.CreateBuilder(args);

var startup = new Startup(builder.Configuration);

startup.ConfigureServices(builder.Services);

var app = builder.Build();

startup.Configure(app, app.Environment);

app.MapRazorPages();

app.Run();

Startup 与新的最小托管模型结合使用时,请记住以下差异:

  • Program.cs 控制类的 Startup 实例化和生存期。
  • 注入 Configure 方法中的其他任何服务都需要通过 Program 类手动解析。

ASP.NET Core 5 与 6 托管模型之间的差异

  • 开发模式下,开发人员异常页中间件在默认情况下处于启用状态。
  • 应用名称默认为入口点程序集的名称:Assembly.GetEntryAssembly().GetName().FullName。 使用库中的 WebApplicationBuilder 时,会将应用名称显式更改为库的程序集,以便 MVC 的应用程序部件发现可正常工作。 有关详细说明,请参阅本文档中的更改内容根、应用名称和环境
  • 终结点路由中间件会包装整个中间件管道,因此无需显式调用 UseRoutingUseEndpoints 来注册路由。 UseRouting 仍可用于指定进行路由匹配的位置,但如果应在中间件管道开头匹配路由,则无需显式调用 UseRouting
  • 管道会在任何 IStartupFilter 运行之前创建,因此生成管道时导致的异常对 IStartupFilter 调用链不可见。
  • 某些工具(如 EF 迁移)使用 Program.CreateHostBuilder 访问应用的 IServiceProvider,以在应用上下文中执行自定义逻辑。 这些工具已更新为使用新方法在应用上下文中执行自定义逻辑。 实体框架迁移是采用此方式使用 Program.CreateHostBuilder 的工具示例。 我们正在努力确保更新工具以使用新模型。
  • 创建 WebApplicationBuilder 之后,无法更改任何主机设置,例如应用名称、环境或内容根。 有关更改主机设置的详细说明,请参阅自定义IHostBuilderIWebHostBuilder。 以下突出显示的 API 会引发异常:
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

// WebHost

try
{
    builder.WebHost.UseContentRoot(Directory.GetCurrentDirectory());
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

try
{
    builder.WebHost.UseEnvironment(Environments.Staging);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

try
{
    builder.WebHost.UseSetting(WebHostDefaults.ApplicationKey, "ApplicationName2");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

try
{
    builder.WebHost.UseSetting(WebHostDefaults.ContentRootKey, Directory.GetCurrentDirectory());
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

try
{
    builder.WebHost.UseSetting(WebHostDefaults.EnvironmentKey, Environments.Staging);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

// Host
try
{
    builder.Host.UseEnvironment(Environments.Staging);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

try
{
    // TODO: This does not throw
    builder.Host.UseContentRoot(Directory.GetCurrentDirectory());
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();
  • 无法从 WebApplicationBuilder.HostWebApplicationBuilder.WebHost 使用 Startup 类。 以下突出显示的代码会引发异常:

    var builder = WebApplication.CreateBuilder(args);
    
    try
    {
        builder.Host.ConfigureWebHostDefaults(webHostBuilder =>
        {
            webHostBuilder.UseStartup<Startup>();
        });
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw;    
    }
    
    builder.Services.AddRazorPages();
    
    var app = builder.Build();
    
    var builder = WebApplication.CreateBuilder(args);
    
    try
    {
        builder.WebHost.UseStartup<Startup>();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw;    
    }
    
    builder.Services.AddRazorPages();
    
    var app = builder.Build();
    
  • WebApplicationBuilder (WebApplicationBuilder.Host) 上的 IHostBuilder 实现不会延迟 ConfigureServicesConfigureAppConfigurationConfigureHostConfiguration 方法的执行。 不延迟执行使得使用 WebApplicationBuilder 的代码可以观察到对 IServiceCollectionIConfiguration 进行的更改。 下面的示例仅添加 Service1 作为 IService

    using Microsoft.Extensions.DependencyInjection.Extensions;
    
    var builder = WebApplication.CreateBuilder(args);
    
    builder.Host.ConfigureServices(services =>
    {
        services.TryAddSingleton<IService, Service1>();
    });
    
    builder.Services.TryAddSingleton<IService, Service2>();
    
    var app = builder.Build();
    
    // Displays Service1 only.
    Console.WriteLine(app.Services.GetRequiredService<IService>());
    
    app.Run();
    
    class Service1 : IService
    {
    }
    
    class Service2 : IService
    {
    }
    
    interface IService
    {
    }
    

在上面的代码中,builder.Host.ConfigureServices 回调进行内联调用,而不是延迟到调用了 builder.Build。 这意味着 Service1 会在 Service2 之前添加到 IServiceCollection,从而导致为 IService 解析 Service1

为 ASP.NET Core 6 生成库

现有 .NET 生态系统围绕 IServiceCollectionIHostBuilderIWebHostBuilder 构建扩展性。 这些属性在 WebApplicationBuilder 上作为 ServicesHostWebHost 来提供。

WebApplication 实现了 Microsoft.AspNetCore.Builder.IApplicationBuilderMicrosoft.AspNetCore.Routing.IEndpointRouteBuilder

生成 ASP.NET Core 特定组件时,我们期望库创建者继续面向 IHostBuilderIWebHostBuilderIApplicationBuilderIEndpointRouteBuilder。 这可确保中间件、路由处理程序或其他扩展点可继续跨不同的托管模型正常工作。

常见问题解答 (FAQ)

  • 新的最小托管模型是否减少了功能?

    不是。 新的托管模型在功能上等效于 IHostBuilderIWebHostBuilder 支持的 98% 的方案。 有一些高级方案需要对 IHostBuilder 采用特定解决方法,但我们预计这种情况非常罕见。

  • 通用托管模型是否已弃用?

    不是。 通用托管模型是受到无限支持的替代模型。 通用主机支持新的托管模型,仍是托管基于辅助角色的应用程序的主要方式。

  • 是否必须迁移到新的托管模型?

    不是。 新的托管模型是使用 .NET 6 和更高版本托管新应用的首选方法,但不强制更改现有应用中的项目布局。 这意味着应用可以通过将项目文件中的目标框架从 net5.0 更改为 net6.0,从 .NET 5 升级到 .NET 6。 有关详细信息,请参阅本文中的更新目标框架部分。 但是,建议将应用迁移到新的托管模型,以利用仅适用于新的托管模型的新功能。

  • 是否必须使用顶级语句?

    不是。 新项目模板全都使用顶级语句,不过可以在任何 .NET 6 应用中使用新的托管 API 来托管 Web 服务器或 Web 应用。

  • 将存储为字段的状态放置在 ProgramStartup 类中的哪个位置?

    强烈建议在 ASP.NET Core 应用中使用依赖项注入 (DI) 来流式传输状态。

    有两种方法可用于在 DI 外部存储状态:

    • 将状态存储在其他类中。 存储在类中会采用可以从应用中的任何位置访问的静态状态。

    • 使用由顶级语句生成的 Program 类存储状态。 使用 Program 存储状态是语义方法:

      var builder = WebApplication.CreateBuilder(args);
      
      ConfigurationValue = builder.Configuration["SomeKey"] ?? "Hello";
      
      var app = builder.Build();
      
      app.MapGet("/", () => ConfigurationValue);
      
      app.Run();
      
      partial class Program
      {
          public static string? ConfigurationValue { get; private set; }
      }
      
  • 项,该?

    支持自定义 DI 容器。 有关示例,请参阅自定义依赖项注入 (DI) 容器

  • WebApplicationFactoryTestServer 是否仍可正常工作?

    是。 WebApplicationFactory<TEntryPoint> 是测试新托管模型的方法。 有关示例,请参见使用 WebApplicationFactoryTestServer 进行测试

Blazor

按照本文前面所述的指导将应用更新到 6.0 后,请按照 ASP.NET Core 6.0 的新增功能中的链接采用特定功能。

若要 为 Blazor 应用采用所有新的 6.0 功能,我们建议执行以下过程:

  • 通过一个 Blazor 项目模板创建新的 6.0 Blazor 项目。 有关详细信息,请参阅用于 ASP.NET Core Blazor 的工具
  • 将应用的组件和代码移动到 6.0 应用,进行修改以采用新的 6.0 功能。

更新 Docker 映像

对于使用 Docker 的应用,请更新 Dockerfile FROM 语句和脚本。 使用包含 ASP.NET Core 6.0 运行时的基础映像。 请考虑 ASP.NET Core 5.0 和 6.0 之间的以下 docker pull 命令差异:

- docker pull mcr.microsoft.com/dotnet/aspnet:5.0
+ docker pull mcr.microsoft.com/dotnet/aspnet:6.0

请参阅GitHub问题中断性变更:默认控制台记录器格式设置为 JSON

对 ASP.NET Core Razor SDK 的更改

Razor 编译器现在利用新的源生成器功能从项目中的 Razor 视图和页面生成编译的 C# 文件。 在以前的版本中:

  • 编译依赖于 RazorGenerateRazorCompile 目标来生成生成的代码。 这些目标不再有效。 在 .NET 6 中,对编译器的单个调用支持代码生成和编译。 RazorComponentGenerateDependsOn 仍受支持,可指定生成运行之前所需的依赖项。
  • 生成了单独的 Razor 程序集 (AppName.Views.dll),其中包含应用程序中编译的视图类型。 此行为已弃用,会生成单个程序集 AppName.dll,其中包含应用类型和生成的视图。
  • AppName.Views.dll 中的应用类型是公共的。 在 .NET 6 中,应用类型处于 AppName.dll 中,不过是 internal sealed。 对 AppName.Views.dll 进行类型发现的应用无法对 AppName.dll 进行类型发现。 下面显示了 API 更改:
- public class Views_Home_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
+ internal sealed class Views_Home_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>

进行以下更改:

  • 以下属性不再适用于单步编译模型。
    • RazorTargetAssemblyAttribute
    • RazorTargetName
    • EnableDefaultRazorTargetAssemblyInfoAttributes
    • UseRazorBuildServer
    • GenerateRazorTargetAssemblyInfo
    • GenerateMvcApplicationPartsAssemblyAttributes

有关详细信息,请参阅 Razor 编译器不再生成 Views 程序集

项目模板使用 Duende Identity Server

Project模板现在使用 Duende Identity 服务器。 有关迁移指南,请参阅 IdentityServer4 v4.1 到 Duende IdentityServer v5

重要

Duende Identity Server 是具有互惠许可协议的开放源代码产品。 如果计划在生产中使用 Duende Identity Server,则可能需要从 Duende Software 获取商业许可证并支付许可证费用。 有关详细信息,请参阅 Duende Software:许可证

若要了解如何使用Microsoft Azure Active DirectoryASP.NET Core Identity,请参阅Identity (dotnet/aspnetcore GitHub 存储库)

将名为 KeysDbSet<Key> 属性添加到每个 IdentityDbContext,以满足 IPersistedGrantDbContext 的更新版本中的新要求。 密钥需要包含在与 Duende Identity Server 存储的协定中。

public DbSet<Key> Keys { get; set; }

注意

必须为 Duende Identity Server 重新创建现有迁移。

迁移到 ASP.NET Core 6.0 的代码示例

迁移到 6.0 中新的最小托管模型的代码示例

查看中断性变更

请参阅以下资源:

空引用类型 (NRT) 和 .NET 编译器 Null 状态静态分析

ASP.NET Core 项目模板使用空引用类型 (NRT),.NET 编译器会执行 Null 状态静态分析。 这些功能随 C# 8 一起发布,默认已为使用 ASP.NET Core 6.0 (C# 10) 或更高版本生成的应用启用。

.NET 编译器的 Null 状态静态分析警告可充当本地更新文档示例或示例应用的指示,也可忽略。 可以通过在应用的项目文件中Nullable 设置为 disable 来禁用 Null 状态静态分析,建议仅将其用于文档示例和示例应用(如果编译器警告在你了解 .NET 时会分散你的注意力)。 不建议在生产项目中禁用 Null 状态检查。

有关 NRT、MSBuild Nullable 属性和更新应用(包括 #pragma 指南)的详细信息,请参阅 C# 文档中的以下资源:

ASP.NET CORE模块 (ANCM)

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

其他资源