.net core 2.2 在2019年末将停止维护,3.0也会在2020年初停止维护,这就需要我们将.net core 2.2 升级到3.0提上日程。本记录只是记录自己升级中遇到得问题以及解决方法,并不是完整的升级攻略。如需了解更多请查看参考文章

基础框架升级

        1.在项目文件中,将目标框架名字对象(TFM)更新为 netcoreapp3.1:

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

<PropertyGroup>
-    <TargetFramework>netcoreapp2.1</TargetFramework>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

</Project>   

具体操作,在VS中将所有<TargetFramework>netcoreapp2.1</TargetFramework>整个解决方案替换成<TargetFramework>netcoreapp3.1</TargetFramework>
        2.更新以前的 Startup.Configure 代码
            2.1 app.UseMvc 方法改变。 下面的代码是典型的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?}");
    });
}

对于大多数应用,对 UseAuthentication、UseAuthorization和 UseCors 的调用必须出现在对 UseRouting 的调用之间,然后 UseEndpoints 才有效。注:这里UseCors,UseAuthentication 都要放在UseRouting,UseEndpoints中间。

           2.2 跨域配置AddCors改变。 跨域这里有两个改法一个是去掉.AllowCredentials(),在跨域不传递cookie得情况,一种是通过WithOrigins指定具体得目标网站地址。最后不要忘了在Config中app.UseCors()
原代码:

services.AddCors(options => options.AddPolicy("AllowAllMethods",
        builder => builder
        .AllowAnyMethod()
        .AllowAnyHeader()
        //允许所有地址
        .AllowAnyOrigin()
        .AllowCredentials()
        //设置 Access-Control-Max-Age 时间 8小时 
        .SetPreflightMaxAge(TimeSpan.FromSeconds(8 * 60 * 60))));

新代码:

services.AddCors(options => options.AddPolicy("AllowAllMethods",
        builder => builder
        .AllowAnyMethod()
        .AllowAnyHeader()
        //允许所有地址
        .AllowAnyOrigin()
        //.AllowCredentials()
        //设置 Access-Control-Max-Age 时间 8小时 
        .SetPreflightMaxAge(TimeSpan.FromSeconds(8 * 60 * 60))));

services.AddCors(options => options.AddPolicy("AllowAllMethods",
        builder => builder
        .AllowAnyMethod()
        .AllowAnyHeader()
        //允许指定地址
        .WithOrigins("http://www.baidu.com")
        .AllowCredentials()
        //设置 Access-Control-Max-Age 时间 8小时 
        .SetPreflightMaxAge(TimeSpan.FromSeconds(8 * 60 * 60))));

app.UseRouting();

//通过终结点路由,CORS 中间件必须配置为在对 UseRouting 和 UseEndpoints的调用之间执行。 配置不正确将导致中间件停止正常运行。
app.UseCors();

app.UseAuthentication();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers().RequireCors("AllowAllMethods");
});

           2.3 IHostingEnvironment 已被弃用 使用IWebHostEnvironment 替换。 需要添加引用 using Microsoft.Extensions.Hosting;

引用组件得升级

3.1 Newtonsoft.Json升级。
需要在项目文件中添加程序包 <PackageReference Include=”Microsoft.AspNetCore.Mvc.NewtonsoftJson” Version=”3.1.0” />
原代码:

services
    .AddMvc()
    .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new ConverterContractResolver())
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

新代码:

services
    .AddControllersWithViews()
    .AddNewtonsoftJson(options =>
    {
        options.SerializerSettings.ContractResolver = new ConverterContractResolver();
    });

3.2 版本控制Microsoft.AspNetCore.Mvc.Versioning升级
需要在项目文件中添加程序包<PackageReference Include=”Microsoft.AspNetCore.Mvc.Versioning” Version=”4.1.1” />

3.3 swagger 升级。具体请查看asp.net core 3.0 中使用 swagger
程序包升级到 <PackageReference Include=”Swashbuckle.AspNetCore” Version=”5.00-rc4” />
添加程序包 <PackageReference Include=”Microsoft.OpenApi” Version=”1.1.4” /> 该包将一些属性规范化
原代码

//获取版本信息
var appsetting = Configuration.GetSection("AppSettings").Get<MyHostAppSettings>();
apiVersions = appsetting.ApiVersion;

//添加版本控制信息
services.AddApiVersioning(options =>
{
    options.ApiVersionReader = new HeaderApiVersionReader("api-version");
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.ReportApiVersions = true;
});
services
.AddSwaggerGen(options =>
{
    foreach (var apiVersion in apiVersions)
    {
        options.SwaggerDoc($"v{apiVersion}",
            new Info
            {
                Title = $"{Configuration["Service:Title"]} 版本{apiVersion}",
                Version = $"v{apiVersion}",
                Description = $"{Configuration["Service:Description"]}",
                Contact = new Contact
                {
                    Name = Configuration["Service:Contact:Name"],
                    Email = Configuration["Service:Contact:Email"]
                }
            });
    }
    var xmlFiles = Configuration["Service:XmlFiles"].Split(',').ToList();
    foreach (var xmlFile in xmlFiles)
    {
        var xmlpath = Path.Combine(basePath, xmlFile);
        options.IncludeXmlComments(xmlpath, true);
    }
    //设置bearer认证
    var security = new Dictionary<string, IEnumerable<string>> { { "Bearer", new string[] { } }, };
    options.AddSecurityRequirement(security);//添加一个必须的全局安全信息,和AddSecurityDefinition方法指定的方案名称要一致,这里是Bearer。
    options.AddSecurityDefinition("Bearer", new ApiKeyScheme
    {
        Description = "JWT授权(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"",
        Name = "Authorization",//jwt默认的参数名称
        In = "header",//jwt默认存放Authorization信息的位置(请求头中)
        Type = "apiKey"
    });

    options.DocInclusionPredicate((docName, apiDesc) =>
    {
        if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;

        var versions = methodInfo.DeclaringType
            .GetCustomAttributes(true)
            .OfType<ApiVersionAttribute>()
            .SelectMany(attr => attr.Versions);

        var versionList = versions.Select(n => n.ToString()).ToList();

        return versionList.Any(v => $"v{v.ToString()}" == docName);
    });
});

新代码

//添加版本控制信息
services.AddApiVersioning(options =>
{
    options.ApiVersionReader = new HeaderApiVersionReader("api-version");
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.ReportApiVersions = true;
});
services
.AddSwaggerGen(options =>
{
    foreach (var apiVersion in apiVersions)
    {
        options.SwaggerDoc($"v{apiVersion}",
            new Microsoft.OpenApi.Models.OpenApiInfo
            {
                Title = $"{Configuration["Service:Title"]} 版本{apiVersion}",
                Version = $"v{apiVersion}",
                Description = $"{Configuration["Service:Description"]}",
                Contact = new OpenApiContact
                {
                    Name = Configuration["Service:Contact:Name"],
                    Email = Configuration["Service:Contact:Email"]
                }
            });
    }
    var xmlFiles = Configuration["Service:XmlFiles"].Split(',').ToList();
    foreach (var xmlFile in xmlFiles)
    {
        var xmlpath = Path.Combine(basePath, xmlFile);
        options.IncludeXmlComments(xmlpath, true);
    }
    //设置bearer认证

    //var security = new Dictionary<string, IEnumerable<string>> { { "Bearer", new string[] { } }, };
    options.AddSecurityRequirement(new OpenApiSecurityRequirement() {
        {
            new OpenApiSecurityScheme(){
            Reference = new OpenApiReference()
            {
                Id = "Bearer",
                Type = ReferenceType.SecurityScheme
            }
        },Array.Empty<string>() }
    });//添加一个必须的全局安全信息,和AddSecurityDefinition方法指定的方案名称要一致,这里是Bearer。
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "JWT授权(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"",
        Name = "Authorization",//jwt默认的参数名称
        In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
        Type = SecuritySchemeType.ApiKey
    });

    options.DocInclusionPredicate((docName, apiDesc) =>
    {
        if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;

        var versions = methodInfo.DeclaringType
            .GetCustomAttributes(true)
            .OfType<ApiVersionAttribute>()
            .SelectMany(attr => attr.Versions);

        var versionList = versions.Select(n => n.ToString()).ToList();

        return versionList.Any(v => $"v{v.ToString()}" == docName);
    });
});

3.4 打开nuget包管理器查看是否还有可从2.1.1升级到3.1得包。有的话升级。 

docker 变更

原 docker 文件

FROM mcr.microsoft.com/dotnet/core/aspnet:2.1-stretch-slim AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/core/sdk:2.1-stretch AS build

新 docker 文件

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build

参考文章

从 ASP.NET Core 3.0 迁移到 3.1
从 ASP.NET Core 2.2 迁移到3.0
asp.net core 3.0 中使用 swagger
启用 ASP.NET Core 中的跨域请求
ASP.NET Core 2.2 -> 3.0 upgrade. env.IsDevelopment() not found
Where did IMvcBuilder AddJsonOptions go in .Net Core 3.0?