从壹开始学习NetCore 44 ║ 最全的 netcore 3.0 升级实战方案

缘起

1、哈喽大家中秋节(后)好呀!感觉已经好久没有写文章了,但是也没有偷懒哟,我的视频教程《系列一、NetCore 视频教程(Blog.Core)》也已经录制八期了,还在每周末同步更新中,欢迎大家多多指教。

2、除此之外呢,我也在平时的时间帮朋友开发了一个小项目,就是使用 .net mvc+vue+ele+mongo 框架写的项目,之前一直想着用mvc结合着vue写,这次也终于上手了,不过是一个小的demo,因为是朋友的项目,所以就不开源了。

image

** ✔ 如果要使用 netcore 3.0 正式版的话,一定要更新 vs2019 最新版:16.3.0**

言归正传,👉 从2018年8月就开始听说 netcore 要准备3.0了,👉 到了近期 v3.0.0-preview9 的发布(截止目前,3.0已经发布,地址 https://dotnet.microsoft.com/download),官方也最终定稿不会再更新了,(这里存疑,不过功能更新至少是不会有了),不过9月23号会发布最终版本, 👉 接着马上 在下周 9月23日至25日 .NET Conf 社区大会上,会正式推出 netcore3.0 版本, (最后 👉 微软会将 .netcore 和 .net 进一步融合,推出完美跨平台 net 5.0 版本,这里暂时先不说),单单从这一年里 netcore 3.0 的快速发展、迭代以及接受用户的反馈进一步修改中,我们就能感觉的到,微软是如何的有希望并且有信心在未来的发展中,将微软系产品进一步融入到广大开发者的心中,我们也要有信心微软能做到这一点。

前言

在netcore 3.0 马上要到来之际,我也要尝尝鲜,我肯定不是第一个吃螃蟹的人,博客园这两个月也是一直轰轰烈烈的进行 3.0 的更新和迭代,不过过程是怎样的吧,至少结果目前还是可以的,也可以作为一个成功案例给大家提供一些建议和思路。

感觉尝试就是成功的一半,所以我在中秋节这两天,也把 Blog.Core 项目给提升到了 3.0 版本,大家现在看的我的在线地址(国外的服务,可能加载比较慢,后期会做处理 http://apk.neters.club/index.html) 就是netcore 3.0 的,总体看起来,可能没有什么差别,而且运行中也没有发现任何问题(管理后台 http://vueadmin.neters.club/),不过这次官方更新的东西还是稍微挺多的,所以我这里就统一做下记录,方便大家吧,希望每一个在使用 netcore 的小伙伴都能从这里得到一些帮助,虽然官网也有一些记录,但是我看了看,英文的可能有些小伙伴不好理解,尽管有中文翻译版,可是看着不是很通顺,而且也不是很全,大家可以看看:地址

当然不仅仅包括下边的这几点,我还在慢慢更新,如果你使用到了我 blog.core 项目中没有用到的技术,并且自己在更新 3.0 的时候出现了问题,可以和我聊聊,我在下边补充下,争取达到一个最全的解决方案合集。

image

好啦,废话到此结束,马上开始今天的迁移报告内容!🌈🌈🌈

零、NetCore3.0 有哪些新特性

image

netcore 1.0 到 2.0 主要的是网络和云服务的升级,那 net core 从2.0 到 3.0 更新的是哪些呢?

这里我就简单的列举了下这一年来netcore 3.0 更新的比较热门的特性,当然还有其他的,因为本篇文章主要是讲解升级实战,所以对以下特性就不过多的铺开讲解。

1、性能、性能、性能,重要的地方说三遍

2、在机器学习,AI等很好的支持

3、对Winform、WPF的支持

4、gRPC的添加

5、支持 API 授权在单页面应用 (Spa) 中提供身份验证、实现 Open ID Connect 的IdentityServer结合。

6、Worker Service 模板,为开发做服务或监控微服务相关Bus

7、Microsoft.Data.SqlClient:独立存在于.NET Framework和.NET Core中

8、ReadyToRun

9、HttpClient支持HTTP/2

10、Json.NET 不在内置在框架内,使用System.Text.Json

11、HostBuilder 替换掉WebHostBuilder

12、Blazor 是一个用于使用 .NET 生成交互式客户端 Web UI 的框架,用c#开发前端

13、.NET Framework不支持.NET Standard 2.1

14、IL linker

15、发布成单个程序 dotnet publish -r win10-x64 /p:PublishSingleFile=true

16、And so on......

那下面我就针对我的 Blog.Core 项目,坐下迁移的说明,一共八个方面,不是很多,大家可以一一对比下。

一、项目启动部分

操作前必备:备份文件,这个很重要,我们要玩儿新花样,肯定要做好备份文件,可别因为升级失败,而不好回退。

当然我的操作是直接操作的 Blog.Core 项目,因为项目在 git 上,如果不成功,就直接回退,这种资源管理工具还是很有必要的。

1、安装SDK

首先可以查看自己的本地 SDK 是什么版本的,比如我的目前只有 2.1和 2.2 :

image

所以,如果我们要升级 3.0 的话,就肯定要安装指定的 SDK 了,下载地址:https://dotnet.microsoft.com/download/visual-studio-sdks ** ✔ 如果要使用 netcore 3.0 正式版的话,一定要更新 vs2019 最新版:16.3.0**

image

选择指定版本的 SDK ,然后进行安装,最后我们就可以看到我们的本地已经安装好了:

image

这里我们可以看到我们的 3.0 的 SDK 已经安装好了,最后再做个验证,就是在我们的 VS 2019 中,查看是否有 3.0 的框架:

image

竟然没有??!!别慌,这里有两个方法:

1、工具 -> 选项 -> 项目与解决方案 -> 右侧,勾选预览版(这个方案是2019 最旧版本的,已取消请忽略)。

image

2、在工具 -> 选项 -> 环境里(正规是使用这个):

image

然后我们把 vs 重新启动一下,发现已经有了:

image

安装好了 SDK,我们就已经是成功了一半了,下边我们就正式开始升级打怪之路。

但是这里还有一个问题,就是打开的项目属性里,虽然有了 3.0 的框架,但是新建的项目,依然没有 3.0 的部分,那这个是为什么呢?

image

这里网上的方案是:不要用preview8或者9,这两个版本出不来core3.0的选项,preview7没有问题。如果非要用最新版,可以用dotnet new创建项目,或者等下星期的 net core 3.0正式版出来,这样就不用来来回回勾选了。

Tips:感谢 @迷失的猫叔 给出建议

https://dotnet.microsoft.com/download/dotnet-core/3.0
这个页面的Tips已经说了,有可能下周就是Core3.0和VS 2019的16.3一起发布,猜测应该就是更新就行了,目前我的VS版本是16.2.5

更新:确实现在已经创建了,要注意:

1、安装 netcore 3.0 正式版;

2、更新 vs2019 16.3.0 最新版;

3、重启 vs2019 即可;

image

2、更新框架以及所有依赖

刚刚我们已经成功的安装好了 3.0 的 SDK ,那接下来就是正式开始升级项目,首先呢,就是需要更新我们的目标框架,这里有两种方法:

第一种是直接修改我们的项目文件 .csproj ,修改节点 <TargetFramework>netcoreapp3.0</TargetFramework>,并移除关于 Aspnetcore 2.2 相关的包;

第二种就是直接右键项目,属性,应用程序,修改目标框架到 netcore 3.0 就行,就是上文截图中显示的那个,我个人采用的是这种方法。

image

直接手动删除即可

记得要把项目从底层开始更新,比如从 Model 层和 Common 层开始更新,然后最后更新 API 层,就是从下向上,(这里有个小问题,就是出现修改了,CTRL S 保存后,又重新回到2.2了,可以重启下项目,重启下vs就行了)。

代码修改对比图:

image

(netcore 3.0 修改sdk框架)

接下来,就是把项目中用到的所有nuget包都更新到最新的版本,这里再强调一句,一定是最新的,包括预发行rc版!!!,因为有些是为了迎接 netcore 3.0,做了相应的修改,比如下午说到的 swagger ,一定要更新到 5.0+版本。

到了这里,我们的项目已经把框架和依赖升级完成了,是不是很简单,重新编译,运行,这里肯定会有错误,别着急,接下来我们就进一步修改 Code 中出现的bug。

3、宿主机变化(Program.cs)

netcore 3.0,对 host 做了调整,底层没有发生太多的变化,这里不细说,主要说要修改的地方,更新的内容,我会在视频里,详细给大家讲解。

在 Program.cs 文件中,修改HostBuilder生成方法,注意在main 方法里引用也要做相应的修改

代码修改对比图:

image

(Program.cs 修改 host 宿主机)

CODE:


 public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => {
         webBuilder
           .UseStartup<Startup>()
           .UseUrls("http://localhost:8081") //这里是配置log的
           .ConfigureLogging((hostingContext, builder) => {
               builder.ClearProviders();
               builder.SetMinimumLevel(LogLevel.Trace);
               builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
               builder.AddConsole();
               builder.AddDebug();
           });
     });


4、Host 环境变量(Startup.cs)

从上边我们也可以看得出来,官方更新了 host ,所以自然而然的,也更新了部分的命名空间,这样就出现了一个问题:

当 Microsoft.Extensions.Hosting 在 2.1 中被引入时,某些类型 IHostingEnvironmentIApplicationLifetime 是从 Microsoft.AspNetCore.Hosting 复制的。某些 3.0 更改会导致应用同时包含 Microsoft.Extensions.Hosting 和 Microsoft.AspNetCore.Hosting 两个命名空间。当同时引用两个命名空间时,对这些重复类型的任何使用都会导致"不明确的引用"编译器错误。

所以官方就对某些命名空间和类做了修改:

Obsolete types (warning):

Microsoft.Extensions.Hosting.IHostingEnvironment
Microsoft.AspNetCore.Hosting.IHostingEnvironment
Microsoft.Extensions.Hosting.IApplicationLifetime
Microsoft.AspNetCore.Hosting.IApplicationLifetime
Microsoft.Extensions.Hosting.EnvironmentName
Microsoft.AspNetCore.Hosting.EnvironmentName

New types:

Microsoft.Extensions.Hosting.IHostEnvironment
Microsoft.AspNetCore.Hosting.IWebHostEnvironment : IHostEnvironment
Microsoft.Extensions.Hosting.IHostApplicationLifetime
Microsoft.Extensions.Hosting.Environments

这个不用记忆,到时候使用的时候,会有提示的,那我们的项目中,有哪个地方需要修改呢,就是配置中间件的时候有一个环境变量配置需要修改下:

image

1、将 IHostingEnvironment env 改成 IWebHostEnvironment env,这个时候会报错,因为命名空间变了;

2、所以需要引用新的命名空间: using Microsoft.Extensions.Hosting;

到了这里,我们就完全修改好了宿主机的部分,现在项目还不能正常的使用,还需要继续修改 mvc 部分,别着急,慢慢往下看。

二、MVC 部分

刚刚我们修改了宿主机 host ,启动项目的时候,还是会有错误,主要提示我们的中间件 .UseMvc() 已经不能被使用了,3.0后,对mvc做了较大的修改,主要从两个方面,一个是服务注册,一个是中间件的拆分:

1、MVC 服务注册(Startup.cs)

在 netcore 3.0 中,官方对 mvc 服务做了细分,主要有以下几个部分:

  services.AddMvc();// 我们平时2.2使用的,最全面的mvc服务注册
  services.AddMvcCore();// 稍微精简的mvc注册
  services.**AddControllers**();// 适用于api的mvc部分服务注册
  services.AddControllersWithViews();//含有api和view的部分服务注册
  services.AddRazorPages();//razor服务注册

我们看出来,如果我们的项目是webapi的,那只需要注册 .AddControllers() 这个就够了,.AddMvc() 里边服务太多,会造成浪费,而大材小用。

代码修改对比图:

image

2、MVC 中间件的拆分(Startup.cs)

除了上边的 mvc 服务注册以外,我们还需要对 UseMvc() 中间件做修改。

官方已经正式去掉了Mvc()这个短路中间件,取代他的是 .UseEndpoints() 方法,我们可以做以下修改:

代码修改对比图:

image

CODE:


 app.UseRouting();//路由中间件 // 短路中间件,配置Controller路由
 app.UseEndpoints(endpoints => {
     endpoints.MapControllerRoute(
         name: "default",
         pattern: "{controller=Home}/{action=Index}/{id?}");
 });

到了这里,我们已经完成了 netcore 2.2 到 net core 3.0 的最简单的升级,如果你想尝试下,可以自己手动建立一个空的 2.2 项目,实现到 3.0 的迁移,我们运行项目,可以看到已经成功的启动起来,还是很成功的。这里要注意下中间件的顺序,一般的顺序是这样的:

 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?}");
  });

那是不是到了这里已经完成了呢,答案当然是否定的,我们的项目不可能这么简单,肯定还会有其他的依赖,还有各种各样的中间件,那我们在升级的过程中,还会有哪些地方需要做处理呢,就比如下边的这些。

三、Swagger 部分

在 netcore 3.0 中,要求我们使用的是 swagger 5.0 ,而且变化的内容也挺多的,但是原理和思路都是一样的,大家一看就知道了,所以我就直接贴代码了。

1、代码修改对比图(ConfigureServices)

这次的修改,主要是服务的注册部分,中间件没有变化,所以我们直接在 startup.cs 中的 configureService 中,做下调整:

这里要注意下,需要引用最新的5.0两个 Nuget 包:Swashbuckle.AspNetCore 和 Swashbuckle.AspNetCore.Filters,这里用的是新的类Openinfo

image

2、修改后的完整代码


services.AddSwaggerGen(c => { typeof(ApiVersions).GetEnumNames().ToList().ForEach(version => { // swagger文档配置
        c.SwaggerDoc(version, new OpenApiInfo
        {
            Version = version,
            Title = $"{ApiName} 接口文档",
            Description = $"{ApiName} HTTP API " + version,
            Contact = new OpenApiContact { Name = ApiName, Email = "Blog.Core@xxx.com", Url = new Uri("https://www.jianshu.com/u/94102b59cc2a") },
            License = new OpenApiLicense { Name = ApiName, Url = new Uri("https://www.jianshu.com/u/94102b59cc2a") }
        }); // 接口排序
        c.OrderActionsBy(o => o.RelativePath);
    }); // 配置 xml 文档
    var xmlPath = Path.Combine(basePath, "Blog.Core.xml");
    c.IncludeXmlComments(xmlPath, true); var xmlModelPath = Path.Combine(basePath, "Blog.Core.Model.xml");
    c.IncludeXmlComments(xmlModelPath);

    c.OperationFilter<AddResponseHeadersFilter>();
    c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>(); // 很重要!这里配置安全校验,和之前的版本不一样
    c.OperationFilter<SecurityRequirementsOperationFilter>(); // 开启 oauth2 安全描述
    c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
    {
        Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey
    });
});

代码中,变化比较大的地方,我已经用红色标注,某些又做了注解,不过大部分的内容还是和之前是一样的,相信大家都能看得懂。

四、Autofac 部分

关于依赖注入框架 Autofac 的变化,整体来说不是很大,主要是在依赖容器的使用上,在 2.2 的时候,我们是直接修改的的 ConfigureServices ,然后将容器实例给 return 出去,但是 3.0 之后,ConfigureServices 不能是返回类型了,只能是 void 方法,那我们就不用 return 出去了,官方给我们提供了一个服务提供上工厂,我们从这个工厂里拿,而不是将服务配置 return 出去。

1、代码修改对比图

1、首先我们需要在 Program.cs 中的 CreateHostBuilder 中,添加Autofac的服务工厂:

image

2、然后在 startup.cs 文件中,新建一个 ConfigureContainer(ContainerBuilder builder) 的方法,里边的内容就是我们之前写的 Autofac 的代码,把之前在 configureService 中的代码都删掉。

只不过我们这里是注入了 builder 的实例对象,不用new了,然后也不用 build 容器了,交给了 hotst 帮助我们一起 build。

image

如果有任何看不懂的,请查看我的 Blog.Core 项目中的代码。

2、修改后的完整代码

Startup.cs 中,新增 ConfigureContainer 方法,删除 ConfigureService中,所有有关 Autofac 的内容:


    public void ConfigureContainer(ContainerBuilder builder)
        { var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath; //注册要通过反射创建的组件 //builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();
            builder.RegisterType<BlogCacheAOP>();//可以直接替换其他拦截器
            builder.RegisterType<BlogRedisCacheAOP>();//可以直接替换其他拦截器
            builder.RegisterType<BlogLogAOP>();//这样可以注入第二个 // ※※★※※ 如果你是第一次下载项目,请先F6编译,然后再F5执行,※※★※※

            #region 带有接口层的服务注入

            #region Service.dll 注入,有对应接口
            //获取项目绝对路径,请注意,这个是实现类的dll文件,不是接口 IService.dll ,注入容器当然是Activatore
            try { var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); var assemblysServices = Assembly.LoadFrom(servicesDllFile);//直接采用加载文件的方法  ※※★※※ 如果你是第一次下载项目,请先F6编译,然后再F5执行,※※★※※ //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();//指定已扫描程序集中的类型注册为提供所有其实现的接口。 // AOP 开关,如果想要打开指定的功能,只需要在 appsettigns.json 对应对应 true 就行。
                var cacheType = new List<Type>(); if (Appsettings.app(new string[] { "AppSettings", "RedisCachingAOP", "Enabled" }).ObjToBool())
                {
                    cacheType.Add(typeof(BlogRedisCacheAOP));
                } if (Appsettings.app(new string[] { "AppSettings", "MemoryCachingAOP", "Enabled" }).ObjToBool())
                {
                    cacheType.Add(typeof(BlogCacheAOP));
                } if (Appsettings.app(new string[] { "AppSettings", "LogAOP", "Enabled" }).ObjToBool())
                {
                    cacheType.Add(typeof(BlogLogAOP));
                }

                builder.RegisterAssemblyTypes(assemblysServices)
                          .AsImplementedInterfaces()
                          .InstancePerLifetimeScope()
                          .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy; // 如果你想注入两个,就这么写  InterceptedBy(typeof(BlogCacheAOP), typeof(BlogLogAOP)); // 如果想使用Redis缓存,请必须开启 redis 服务,端口号我的是6319,如果不一样还是无效,否则请使用memory缓存 BlogCacheAOP
                          .InterceptedBy(cacheType.ToArray());//允许将拦截器服务的列表分配给注册。 
                #endregion

                #region Repository.dll 注入,有对应接口
                var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); var assemblysRepository = Assembly.LoadFrom(repositoryDllFile);
                builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces();
            } catch (Exception ex)
            { throw new Exception("※※★※※ 如果你是第一次下载项目,请先对整个解决方案dotnet build(F6编译),然后再对api层 dotnet run(F5执行),\n因为解耦了,如果你是发布的模式,请检查bin文件夹是否存在Repository.dll和service.dll ※※★※※" + ex.Message + "\n" + ex.InnerException);
            } #endregion
            #endregion

            #region 没有接口层的服务层注入

            ////因为没有接口层,所以不能实现解耦,只能用 Load 方法。 ////注意如果使用没有接口的服务,并想对其使用 AOP 拦截,就必须设置为虚方法 ////var assemblysServicesNoInterfaces = Assembly.Load("Blog.Core.Services"); ////builder.RegisterAssemblyTypes(assemblysServicesNoInterfaces);

            #endregion

            #region 没有接口的单独类 class 注入
            ////只能注入该类中的虚方法
            builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love)))
                .EnableClassInterceptors()
                .InterceptedBy(typeof(BlogLogAOP)); #endregion

            //这里不要再 build 了 //var ApplicationContainer = builder.Build();
 }

program.cs


 public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args)
     .UseServiceProviderFactory(new AutofacServiceProviderFactory()) //<--NOTE THIS
     .ConfigureWebHostDefaults(webBuilder => {
         webBuilder
           .UseStartup<Startup>()
           .UseUrls("http://localhost:8081")
           .ConfigureLogging((hostingContext, builder) => {
               builder.ClearProviders();
               builder.SetMinimumLevel(LogLevel.Trace);
               builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
               builder.AddConsole();
               builder.AddDebug();
           });
     });

3、变化的核心点

就是将我们的Autofac的容器,从 configureService 中,转向了我们的宿主机中了,步骤是:

1、删除 ConfigureService 中的所有 Autofac 配置内容;

2、将刚刚删除的配置内容,拷贝到新建一个 ConfigureContainer 方法中;

3、在 ConfigureContainer 方法中,不要进行 build 操作,然后 Main 入口方法中的 Build() 去执行。

4、在 Program.cs 的 CreateHostBuilder 中,新增服务工厂实例。

好了,到现在,我们可以尝试看看 Autofac 依赖注入框架,已经可以正常的使用了。

五、Sqlsugar 部分

随着netcore 3.0 的更新,sqlsugar当然也要做相应的优化处理,主要是为了配合 3.0 做处理,作者凯旋兄还是很负责的,及时做了调整,目前 sqlsugar 的版本是 5.0.10 ,我们如果使用 netcore 3.0 的话,就必须要使用。

1、这个 5.0.10 的版本,如果不使用的话,可能会有一个映射错误:

image

如果遇到了这个错误,直接不要问,更新到最新版本就行。

2、如果更新了以后,发现还有错误,一个《未将对象引用到对象的实例》:

image

这个时候,你可以尝试重新生成下数据库,好像只需要创建下表结构就行,数据可以导入,记得做好生产环境数据库备份

其他还没有发现什么问题。

六、Authorization 部分(基本解决)

1、swagger是如何增加校验功能呢?

这个地方其实很简单,刚刚在讲 swagger 的时候,我也说到了,有一个地方需要我们注意, 就是安全校验的配置上,现在发生了变化,从服务添加变成了过滤器:

image

具体的代码,在上边讲 swagger 的时候,已经粘贴完整了,你可以直接复制即可。

2、接口上又是如何配置策略权限的呢?

之前我的 Blog.Core 项目使用了权限过滤器公约,这样就算 controller 没有配置 Authorize 的话,也会默认采用这种权限过滤器,感觉很方便。

但是现在不行了,必须要在每一个 controller 上配置,才能在 swagger 中出现那个 小锁 的标志,所以我又都在 controller 上,加上了 [Authorize(Permissions.Name)]

如果不配置的话,是没有小锁标志,也就不会启动权限认证的作用的,只有配置了的才有:

image

但是好像不仅如此,就算是加上了特性好像也不行,这一块我也发现有点儿问题,好像目前在controller 上配置 [Authorize(Policy = Permissions.Name)] 并不能实现授权的目的,就算是用官网的在短路节点, .RequireAuthorization(new AuthorizeAttribute() { Policy = Permissions.Name, }); 配置也不行,所以,目前我的项目授权部分起作用的还是我的授权公约过滤器,并不是加的特性,我还在调试,如果你正好写到 netcore 3.0 授权策略了,请评论,不胜感激。这是我的博问:https://q.cnblogs.com/q/120091/

更新:2019-10-03

在上边博问里,已经有人回答了,可以看看,总结来说是这样的,不用说其他的,直接看代码吧:


 // 重写异步处理程序
        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
        { /* * .netcore3.0 启用EndpointRouting后,权限filter不再添加到ActionDescriptor ,而将权限直接作为中间件运行,
             * 同时所有filter都会添加到endpoint.Metadata。因此,文中的
             * context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext不再成立。
             * 
             * 解决方案有两个:
             * 
             * 首先必须在 controller 上进行配置 Authorize ,可以策略授权,也可以角色等基本授权
             * 
             * 1、开启公约, startup 中的全局授权过滤公约:o.Conventions.Insert(0, new GlobalRouteAuthorizeConvention());
             * 
             * 2、不开启公约,使用 IHttpContextAccessor ,也能实现效果,但是不能自定义返回格式,详细看下边配置; */

            // 将最新的角色和接口列表更新
            var data = await RoleModulePermissionServices.GetRoleModule(); var list = (from item in data where item.IsDeleted == false
                        orderby item.Id select new PermissionItem
                        {
                            Url = item.Module?.LinkUrl,
                            Role = item.Role?.Name,
                        }).ToList();

            requirement.Permissions = list; //从AuthorizationHandlerContext转成HttpContext,以便取出表求信息
            var filterContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext); var httpContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext)?.HttpContext; if (httpContext == null)
            {
                httpContext = _accessor.HttpContext;
            } //请求Url
            if (httpContext != null)
            { var questUrl = httpContext.Request.Path.Value.ToLower(); //判断请求是否停止
                var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>(); foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
                { if (await handlers.GetHandlerAsync(httpContext, scheme.Name) is IAuthenticationRequestHandler handler && await handler.HandleRequestAsync())
                    {
                        context.Fail(); return; //自定义返回数据
                        //var payload = JsonConvert.SerializeObject(new { Code = "401", Message = "很抱歉,您无权访问该接口!" });
                        //httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
                        //filterContext.Result = new JsonResult(payload);
                        //context.Succeed(requirement);
                        //return;

                    }
                } //判断请求是否拥有凭据,即有没有登录
                var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); if (defaultAuthenticate != null)
                { var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); //result?.Principal不为空即登录成功
                    if (result?.Principal != null)
                    {

                        httpContext.User = result.Principal; //权限中是否存在请求的url //if (requirement.Permissions.GroupBy(g => g.Url).Where(w => w.Key?.ToLower() == questUrl).Count() > 0) //if (isMatchUrl)
                        if (true)
                        { // 获取当前用户的角色信息
                            var currentUserRoles = (from item in httpContext.User.Claims where item.Type == requirement.ClaimType select item.Value).ToList(); var isMatchRole = false; var permisssionRoles = requirement.Permissions.Where(w => currentUserRoles.Contains(w.Role)); foreach (var item in permisssionRoles)
                            { try { if (Regex.Match(questUrl, item.Url?.ObjToString().ToLower())?.Value == questUrl)
                                    {
                                        isMatchRole = true; break;
                                    }
                                } catch (Exception)
                                { // ignored
 }
                            } //验证权限 //if (currentUserRoles.Count <= 0 || requirement.Permissions.Where(w => currentUserRoles.Contains(w.Role) && w.Url.ToLower() == questUrl).Count() <= 0)
                            if (currentUserRoles.Count <= 0 || !isMatchRole)
                            { // 可以在这里设置跳转页面
 context.Fail(); return; //自定义返回数据 //var payload = JsonConvert.SerializeObject(new { Code = "403", Message = "很抱歉,您无权访问该接口!" }); //httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
                                ////filterContext.Result = new JsonResult(payload);
                                //context.Succeed(requirement); //return;
 }
                        } //判断过期时间(这里仅仅是最坏验证原则,你可以不要这个if else的判断,因为我们使用的官方验证,Token过期后上边的result?.Principal 就为 null 了,进不到这里了,因此这里其实可以不用验证过期时间,只是做最后严谨判断)
                        if ((httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) != null && DateTime.Parse(httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) >= DateTime.Now)
                        {
                            context.Succeed(requirement);
                        } else {
                            context.Fail(); return; //自定义返回数据 //var payload = JsonConvert.SerializeObject(new { Code = "401", Message = "很抱歉,您无权访问该接口!" }); //httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized; //filterContext.Result = new JsonResult(payload); //context.Succeed(requirement); //return;
 } return;
                    }
                } //判断没有登录时,是否访问登录的url,并且是Post请求,并且是form表单提交类型,否则为失败
                if (!questUrl.Equals(requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType))
                {
                    context.Fail(); return; //自定义返回数据 //var payload = JsonConvert.SerializeObject(new { Code = "401", Message = "很抱歉,您无权访问该接口!" }); //httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    ////filterContext.Result = new JsonResult(payload);
                    //context.Succeed(requirement); //return;
 }
            }

            context.Succeed(requirement);
        }

七、JSON 部分

1、接口返回格式

在netcore 3.0 中,它内置了一个 json 工具—— System.Text.Json,而作为改善 ASP.NET Core 共享框架的工作的一部分,已从 ASP.NET Core 共享框架中删除Json.NET 。 如果你的应用程序使用Newtonsoft.Json特定的功能(如 JsonPatch 或转换器),或者如果它是特定于格式 Newtonsoft.Json的类型,那我们就需要重新引用它。

简单来说,就是 3.0 内置了 Text.Json 框架,你可以直接使用,但是我没有用这个,因为我好像中间出现了一个序列化错误,而且我还要取消默认的驼峰命名,所以我还是采用的之前的 Newtonsoft.json,具体的使用方法请看:

1、如果使用 .net core 3.0 内置的 System.Text.Json ,配置方法如下:

services.AddMvc().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
    options.JsonSerializerOptions.PropertyNamingPolicy = null;
});

2、如果使用 Newtonsoft.Json ,配置方法如下:

services.AddControllers()
    .AddNewtonsoftJson(options =>
        options.SerializerSettings.ContractResolver = new DefaultContractResolver());

2、中文乱码问题

原理就不多说了,直接上代码

public string[] ReturnChinese()
{ var jsonObject = new { chinese = "老张的哲学" }; string aJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(value: jsonObject); string bJsonString = System.Text.Json.JsonSerializer.Serialize(
        value: jsonObject,
        options: new System.Text.Json.JsonSerializerOptions
    {
        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
    }); return new string[]{aJsonString,bJsonString};

}

八、SignalR 部分

这个很简单,官方中间件取消了 UseSignalR 中间件,而放到了 UseEndpoints 短路中间件中,配置如下:


 app.UseRouting();
 app.UseEndpoints(endpoints => {
     endpoints.MapHub<ChatHub>("/api2/chatHub"); 
     endpoints.MapControllerRoute(
         name: "default",
         pattern: "{controller=Home}/{action=Index}/{id?}");
 });

在使用中,会出现json格式的问题,这里可以做下修改,依然使用 Newtonsoft:

1、修改服务注册 services.AddSignalR().AddNewtonsoftJsonProtocol();
2、引用 nuget 包 Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson

九、CORS 部分

CORS变化其实不大,整体来说和 2.2 一样的,具体的按照 之前的写法来写就行。

只是已经不支持向所有域名开放了,会有错误提示:

The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time. Configure the CORS policy by listing individual origins if credentials needs to be supported.”

所以下边的 Policy 可以删除了,其他的不用变化:

 c.AddPolicy("AllRequests", policy => {
      policy
      .AllowAnyOrigin()//允许任何源
      .AllowAnyMethod()//允许任何方式
      .AllowAnyHeader()//允许任何头
      .AllowCredentials();//允许cookie
  });

然后就是要注意中间件的顺序,这里记得还要带上 policy 的名称 ,还是 app.UseCors("LimitRequests");:

image

十、MiniProfiler 部分

这里还有一个问题,就是最新版的nuget包情况下,会报一个错误:

image

这是一个小问题,主要是因为 netcore 3.0 升级以后,miniprofiler 之前版本对 System.Text.json 没有很好的兼容导致,但是官方新版本已经更新,我们只需要升级下版本即可:

1、升级 MiniProfiler 到最新版本 4.1.0;

2、swagger 的 index.html 文件中,对 MiniProfiler 的js引用也要同步更新;

3、保证当前 id 不能为空,可以自己随意定义一个Guid值;

最终的代码是这样的:


<!--1、版本号要与nuget包一致;2、id不能为空-->
<script async="async" id="mini-profiler" src="/profiler/includes.min.js?v=4.1.0+c940f0f28d" data-version="4.1.0+c940f0f28d" data-path="/profiler/" data-current-id="4ec7c742-49d4-4eaf-8281-3c1e0efa8888" data-ids="4ec7c742-49d4-4eaf-8281-3c1e0efa8888" data-position="Left" data-authorized="true" data-max-traces="5" data-toggle-shortcut="Alt+P" data-trivial-milliseconds="2.0" data-ignored-duplicate-execute-types="Open,OpenAsync,Close,CloseAsync">
</script>

多参考官网开源项目:https://github.com/MiniProfiler/dotnet/tree/master/samples

其他补充中

如果你有其他的用到的,是我没有使用到的, 或者我上文没有提到的注意点,

欢迎想问提问和反馈,我会在这里,给你署名写上,让更多的小伙伴可以学会学号。

谢谢。

END、Github && Gitee

https://github.com/anjoy8/Blog.Core

https://gitee.com/laozhangIsPhi/Blog.Core

相关文章:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容