asp.net core 3.0 更新简记

intro

最近把活动室预约项目从 asp.net core 2.2 更新到了 asp.net core 3.0,记录一下,升级踩过的坑以及经验总结,包括但不限于

  • targetframework (netcoreapp2.2 需要更新为 netcoreapp3.0)
  • dependency
  • host/environment
  • mvc
  • routing
  • swagger
  • dockerfile
  • ef(不推荐更新)

targetframework 更新

原来项目里的 netcoreapp2.x 版本需要更新为 netcoreapp3.0,原来有些基于 netstandard2.0 的项目的包如果有包更新之后依赖 netstandard2.1 可能需要更新为 netstandard2.1(非必须,看项目依赖)

dependency

原来在 dotnetcore 2.x 版本时大部分的包以 nuget 的形式提供,可以直接通过 nuget 引用,从 dotnetcore 3.0 开始很多包不再发布 nuget 包,需要通过框架引用来引用包(frameworkreference)

比如在一个类库项目 <project sdk="microsoft.net.sdk"> 里有这么一个引用 <packagereference include="microsoft.aspnetcore.mvc.core" version="2.2.2" />,在 dotnetcore 3.0 并没有发布对应的 nuget 包,需要使用框架引用,示例如下:

<frameworkreference include="microsoft.aspnetcore.app" />

如果是 web 项目 <project sdk="microsoft.net.sdk.web">,sdk 是 web 的话直接把 targetframework 更新的 netcoreapp3.0 就可以了,不需要再添加上面的引用了,项目里 <packagereference include="microsoft.aspnetcore.app"/> 的引用也可以直接移除了

host && environment

原来的 ihostingenvironment 改为了 iwebhostenvironment,原来注入 ihostingenvironment 的地方需要修改为注入 iwebhostenvironment

原来 program 里使用的 webhostbuilder 改为 hostbuilder 并配置 `configurewebhostdefaults `,变更如下:

mvc

服务注册

3.0 里 mvc 对 controllerrazorpages 以及 razorviews 整理,注入服务的时候我们可以只注入自己需要的服务,如果是 webapi 项目可以只添加 controller 需要的服务即可,对应的添加方式:

services.addcontrollers(); // webapi
services.addcontrollerswithviews(); // mvc
services.addrazorpages(); // razorpage

json 配置

asp.net core 3.0 默认使用微软新的 json,但是推荐还是用 newtonsoft.json,比较成熟而且对很多比较特殊的情况都有处理,已然成为了.net 里 json 序列化的事实标准,使用方式如下:

  1. 引用 nuget 包 microsoft.aspnetcore.mvc.newtonsoftjson

  2. 配置使用 newtonsoft.json

services.addcontrollerswithviews()
    .addnewtonsoftjson(options =>
    {
        options.serializersettings.contractresolver = new defaultcontractresolver();
        options.serializersettings.datetimezonehandling = datetimezonehandling.utc; // 设置时区为 utc
    });

rounting

asp.net core 3.0 推荐使用 endpoint rounting

配置方式如下:

           app.usestaticfiles();

            app.useswagger()
                .useswaggerui(c =>
                {
                    // c.routeprefix = string.empty; //
                    c.swaggerendpoint($"/swagger/{applicationhelper.applicationname}/swagger.json", "活动室预约系统 api");
                    c.documenttitle = "活动室预约系统 api";
                });
                
            app.userouting(); // 放在 usestaticfiles 之后

            app.usecors(builder => builder.allowanyheader().allowanymethod().allowanyorigin());

            app.userequestlog();
            app.useperformancelog();

            app.useauthentication();
            app.useauthorization(); // 放在 useauthentication 之后

            app.useendpoints(endpoints =>
            {
                endpoints.mapcontrollers(); // 属性路由
                endpoints.mapcontrollerroute("notice", "/notice/{path}.html", new
                {
                    controller = "home",
                    action = "noticedetails"
                }); // 自定义路由
                endpoints.mapcontrollerroute(name: "arearoute", "{area:exists}/{controller=home}/{action=index}"); // 区域路由
                endpoints.mapcontrollerroute("default", "{controller=home}/{action=index}/{id?}"); // 默认路由
            });

swagger

更新 swagger 依赖到最新的 5.0.0-rc-x 版本(还没发稳定版,需要显示预览版本才能看到)

services.addswaggergen(options =>
{
    options.swaggerdoc(applicationhelper.applicationname, new microsoft.openapi.models.openapiinfo { title = "活动室预约系统 api", version = "1.0" });

    options.includexmlcomments(system.io.path.combine(appcontext.basedirectory, $"{typeof(models.notice).assembly.getname().name}.xml"));
    options.includexmlcomments(system.io.path.combine(appcontext.basedirectory, $"{typeof(api.noticecontroller).assembly.getname().name}.xml"), true);
});

这里没有用到 operationfilter,如果用到了 operationfilter,可能需要引入 swashbuckle.aspnetcore.filters 这个包,详细参考:

docker

dockerfile 里基础镜像需要更新到 3.0

示例 dockerfile:

from mcr.microsoft.com/dotnet/core/sdk:3.0-alpine as build-env
workdir /src

# copy csproj and restore as distinct layers
copy activityreservation.common/*.csproj activityreservation.common/
copy activityreservation.models/*.csproj activityreservation.models/
copy activityreservation.dataaccess/*.csproj activityreservation.dataaccess/
copy activityreservation.business/*.csproj activityreservation.business/
copy activityreservation.helper/*.csproj activityreservation.helper/
copy activityreservation.wechatapi/*.csproj activityreservation.wechatapi/
copy activityreservation.adminlogic/*.csproj activityreservation.adminlogic/
copy activityreservation.api/*.csproj activityreservation.api/
copy activityreservation/activityreservation.csproj activityreservation/

# run dotnet restore activityreservation/activityreservation.csproj
## diff between netcore2.2 and netcore3.0
workdir /src/activityreservation
run dotnet restore

# copy everything and build
copy . .
run dotnet publish -c release -o out activityreservation/activityreservation.csproj

# build runtime image
from mcr.microsoft.com/dotnet/core/aspnet:3.0-alpine

label maintainer="weihanli"
workdir /app
copy --from=build-env /src/activityreservation/out .
expose 80
entrypoint ["dotnet", "activityreservation.dll"]

修改基础镜像一般不会有什么问题,需要注意的是如果 dockerfile 里有用到 dotnet publish 且publish 的项目不在当前目录下,可能会遇到这样的问题,最后找不到要发布的文件。

dotnet core 3.0 cli 有个 breaking change,如果要发布的项目不在当前目录下,在 2.x 版本是会发布到项目文件所在目录下的,但是 3.0 版本会发布在当前目录下,比如说执行dotnet publish -c release ./src/activityreservation.csproj -o out命令:

2.x版本会在 src目录下生成一个 out 文件夹

3.0 版本会在当前目录下生成一个 out 文件夹,out文件夹和 src 同级

详细问题可以参考

ef

ef core 3.0 和 asp.net core 3.0 完全独立,可以在 asp.net core 3.0 的项目里使用 2.x 的 ef core

ef core 3.0 有个 breaking change,不再隐式支持客户端渲染数据,这可以让你更清晰的知道哪些条件和在数据库执行的哪些条件是在本地执行的,但是实际试用下来,还是有很多问题的,在 ef 的基础上封装了一层,试用表达式树来拼接查询条件,但是最后执行的时候会有问题,但是简化后的条件实际上并不会在客户端执行任何过滤操作,所以暂时不推荐试用 ef core 3.0,而且更新之后可能会遇到其他的问题,详见issue https://github.com/aspnet/entityframeworkcore/issues/18025

现在的项目使用 ef core2.1 + asp.net core3.0 运行

more

其他的地方应该也有需要修改的地方,欢迎补充

reference

  • https://github.com/weihanli/activityreservation/pull/28/files