从壹开始学习NetCore 44 ║ 最全的 netcore 3.0 升级实战方案
缘起
1、哈喽大家中秋节(后)好呀!感觉已经好久没有写文章了,但是也没有偷懒哟,我的视频教程《系列一、NetCore 视频教程(Blog.Core)》也已经录制八期了,还在每周末同步更新中,欢迎大家多多指教。
2、除此之外呢,我也在平时的时间帮朋友开发了一个小项目,就是使用 .net mvc+vue+ele+mongo 框架写的项目,之前一直想着用mvc结合着vue写,这次也终于上手了,不过是一个小的demo,因为是朋友的项目,所以就不开源了。
✔ 如果要使用 netcore 3.0 正式版的话,一定要更新 vs2019 最新版:16.3.0
当然你可以先看看3.x到底更新了哪些东西: https://docs.microsoft.com/zh-cn/aspnet/core/release-notes/aspnetcore-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 的时候出现了问题,可以和我聊聊,我在下边补充下,争取达到一个最全的解决方案合集。
好啦,废话到此结束,马上开始今天的迁移报告内容!🌈🌈🌈
零、NetCore3.0 有哪些新特性
netcore 1.0 到 2.0 主要的是网络和云服务的升级,那 net core 从2.0 到 3.0 更新的是哪些呢?
这里我就简单的列举了下这一年来netcore 3.0 更新的比较热门的特性,当然还有其他的,因为本篇文章主要是讲解升级实战,所以对以下特性就不过多的铺开讲解。
先看看官网提到了哪些更新内容:https://docs.microsoft.com/zh-cn/aspnet/core/release-notes/aspnetcore-3.0?WT.mc_id=DOP-MVP-5003704&view=aspnetcore-3.1
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 :
所以,如果我们要升级 3.0 的话,就肯定要安装指定的 SDK 了,下载地址:https://dotnet.microsoft.com/download/visual-studio-sdks
✔ 如果要使用 netcore 3.0 正式版的话,一定要更新 vs2019 最新版:16.3.0
选择指定版本的 SDK ,然后进行安装,最后我们就可以看到我们的本地已经安装好了:
这里我们可以看到我们的 3.0 的 SDK 已经安装好了,最后再做个验证,就是在我们的 VS 2019 中,查看是否有 3.0 的框架:
竟然没有??!!别慌,这里有两个方法:
1、工具 -> 选项 -> 项目与解决方案 -> 右侧,勾选预览版(这个方案是2019 最旧版本的,已取消请忽略)。
2、在工具 -> 选项 -> 环境里(正规是使用这个):
然后我们把 vs 重新启动一下,发现已经有了:
安装好了 SDK,我们就已经是成功了一半了,下边我们就正式开始升级打怪之路。
但是这里还有一个问题,就是打开的项目属性里,虽然有了 3.0 的框架,但是新建的项目,依然没有 3.0 的部分,那这个是为什么呢?
这里网上的方案是:不要用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 即可;
2、更新框架以及所有依赖
刚刚我们已经成功的安装好了 3.0 的 SDK ,那接下来就是正式开始升级项目,首先呢,就是需要更新我们的目标框架,这里有两种方法:
第一种是直接修改我们的项目文件 .csproj ,修改节点 <TargetFramework>netcoreapp3.0</TargetFramework>,并移除关于 Aspnetcore 2.2 相关的包;
第二种就是直接右键项目,属性,应用程序,修改目标框架到 netcore 3.0 就行,就是上文截图中显示的那个,我个人采用的是这种方法。
直接手动删除即可
记得要把项目从底层开始更新,比如从 Model 层和 Common 层开始更新,然后最后更新 API 层,就是从下向上,(这里有个小问题,就是出现修改了,CTRL S 保存后,又重新回到2.2了,可以重启下项目,重启下vs就行了)。
代码修改对比图:
(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 方法里引用也要做相应的修改。
代码修改对比图:
(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 中被引入时,某些类型
IHostingEnvironment
和IApplicationLifetime
是从 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
这个不用记忆,到时候使用的时候,会有提示的,那我们的项目中,有哪个地方需要修改呢,就是配置中间件的时候有一个环境变量配置需要修改下:
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() 里边服务太多,会造成浪费,而大材小用。
代码修改对比图:
2、MVC 中间件的拆分(Startup.cs)
除了上边的 mvc 服务注册以外,我们还需要对 UseMvc() 中间件做修改。
官方已经正式去掉了Mvc()这个短路中间件,取代他的是 .UseEndpoints() 方法,我们可以做以下修改:
代码修改对比图:
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?}"); });
那是不是到了这里已经完成了呢,答案当然是否定的,我们的项目不可能这么简单,肯定还会有其他的依赖,还有各种各样的中间件,那我们在升级的过程中,还会有哪些地方需要做处理呢,就比如下边的这些。
3、注意:中间件的顺序
不多说,注意下中间件的顺序 。
三、Swagger 部分
在 netcore 3.0 中,要求我们使用的是 swagger 5.0 ,而且变化的内容也挺多的,但是原理和思路都是一样的,大家一看就知道了,所以我就直接贴代码了。
1、代码修改对比图(ConfigureServices)
这次的修改,主要是服务的注册部分,中间件没有变化,所以我们直接在 startup.cs 中的 configureService 中,做下调整:
这里要注意下,需要引用最新的5.0两个 Nuget 包(预览版):Swashbuckle.AspNetCore 和 Swashbuckle.AspNetCore.Filters,这里用的是新的类Openinfo
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 安全描述,必须是 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的服务工厂:
2、然后在 startup.cs 文件中,新建一个 ConfigureContainer(ContainerBuilder builder) 的方法,里边的内容就是我们之前写的 Autofac 的代码,把之前在 configureService 中的代码都删掉。
只不过我们这里是注入了 builder 的实例对象,不用new了,然后也不用 build 容器了,交给了 hotst 帮助我们一起 build。
如果有任何看不懂的,请查看我的 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 的版本,如果不使用的话,可能会有一个映射错误:
如果遇到了这个错误,直接不要问,更新到最新版本就行。
2、如果更新了以后,发现还有错误,一个《未将对象引用到对象的实例》:
这个时候,你可以尝试重新生成下数据库,好像只需要创建下表结构就行,数据可以导入,记得做好生产环境数据库备份。
其他还没有发现什么问题。
六、Authorization 部分
1、swagger是如何增加校验功能呢?
这个地方其实很简单,刚刚在讲 swagger 的时候,我也说到了,有一个地方需要我们注意, 就是安全校验的配置上,现在发生了变化,从服务添加变成了过滤器:
具体的代码,在上边讲 swagger 的时候,已经粘贴完整了,你可以直接复制即可。
注意一定要使用 "oauth2" 安全描述:
2、接口上又是如何配置策略权限的呢?
之前我的 Blog.Core 项目使用了权限过滤器公约,这样就算 controller 没有配置 Authorize 的话,也会默认采用这种权限过滤器,感觉很方便。
但是现在不行了,必须要在每一个 controller/action 上配置,才能在 swagger 中出现那个 小锁 的标志,所以我又都在 controller 上,加上了 [Authorize(Permissions.Name)]
如果不配置的话,是没有小锁标志,也就不会启动权限认证的作用的,只有配置了的才有:
3、修改授权处理器PermissionHandler
不用说其他的,直接看代码吧:
// 重写异步处理程序 protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { var httpContext = _accessor.HttpContext; if (!requirement.Permissions.Any()) { var data = await _roleModulePermissionServices.RoleModuleMaps(); var list = (from item in data where item.IsDeleted == false orderby item.Id select new PermissionItem { Url = item.Module?.LinkUrl, Role = item.Role?.Id.ObjToString(), }).ToList(); requirement.Permissions = list; } //请求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 defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); if (defaultAuthenticate != null) { var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); //result?.Principal不为空即登录成功 if (result?.Principal != null) { // 将最新的角色和接口列表更新 // 这里暂时把代码移动到了Login获取token的api里,这样就不用每次都请求数据库,造成压力. // 但是这样有个问题,就是如果修改了某一个角色的菜单权限,不会立刻更新, // 需要让用户退出重新登录,如果你想实时更新,请把下边的注释打开即可. //var data = await _roleModulePermissionServices.RoleModuleMaps(); //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; 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 == "role" 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; } } //判断过期时间(这里仅仅是最坏验证原则,你可以不要这个if else的判断,因为我们使用的官方验证,Token过期后上边的result?.Principal 就为 null 了,进不到这里了,因此这里其实可以不用验证过期时间,只是做最后严谨判断) if ((httpContext.User.Claims.SingleOrDefault(s => s.Type == "exp")?.Value) != null && DateHelper.StampToDateTime(httpContext.User.Claims.SingleOrDefault(s => s.Type == "exp")?.Value) >= DateTime.Now) { context.Succeed(requirement); } else { context.Fail(); return; } return; } } //判断没有登录时,是否访问登录的url,并且是Post请求,并且是form表单提交类型,否则为失败 if (!questUrl.Equals(requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType)) { context.Fail(); return; } } context.Succeed(requirement); }
4、自定义状态返回
1、修改AddAuthentication服务注册
标红的部分
// 开启Bearer认证 services.AddAuthentication(o=> { o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; o.DefaultChallengeScheme = nameof(ApiResponseHandler); o.DefaultForbidScheme = nameof(ApiResponseHandler); }) 添加JwtBearer服务 .AddJwtBearer(o => { o.TokenValidationParameters = tokenValidationParameters; o.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { // 如果过期,则把<是否过期>添加到,返回头信息中 if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) { context.Response.Headers.Add("Token-Expired", "true"); } return Task.CompletedTask; } }; }) .AddScheme<AuthenticationSchemeOptions, ApiResponseHandler>(nameof(ApiResponseHandler), o => { });
2、添加Api返回对象
public class ApiResponse { public int Status { get; set; } = 404; public object Value { get; set; } = "No Found"; public ApiResponse(StatusCode apiCode, object msg = null) { switch (apiCode) { case StatusCode.CODE401: { Status = 401; Value = "很抱歉,您无权访问该接口,请确保已经登录!"; } break; case StatusCode.CODE403: { Status = 403; Value = "很抱歉,您的访问权限等级不够,联系管理员!"; } break; case StatusCode.CODE500: { Status = 500; Value = msg; } break; } } } public enum StatusCode { CODE401, CODE403, CODE404, CODE500 }
3、添加api响应处理器
public class ApiResponseHandler : AuthenticationHandler<AuthenticationSchemeOptions> { public ApiResponseHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { } protected override Task<AuthenticateResult> HandleAuthenticateAsync() { throw new NotImplementedException(); } protected override async Task HandleChallengeAsync(AuthenticationProperties properties) { Response.ContentType = "application/json"; Response.StatusCode = StatusCodes.Status401Unauthorized; await Response.WriteAsync(JsonConvert.SerializeObject(new ApiResponse(StatusCode.CODE401))); } protected override async Task HandleForbiddenAsync(AuthenticationProperties properties) { Response.ContentType = "application/json"; Response.StatusCode = StatusCodes.Status403Forbidden; await Response.WriteAsync(JsonConvert.SerializeObject(new ApiResponse(StatusCode.CODE403))); } }
七、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 一样的,具体的按照 之前的写法来写就行。
只是已经不支持在使用 AllowCredentials 的时候向所有域名开放了,感谢网友提醒@wswind,会有错误提示:
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");:
十、MiniProfiler 部分
服务注册:
services.AddMiniProfiler(options => { options.RouteBasePath = "/profiler"; options.PopupRenderPosition = StackExchange.Profiling.RenderPosition.Left; options.PopupShowTimeWithChildren = true; });
1、升级nuget包,并配合修改js调用
这里还有一个问题,就是最新版的nuget包情况下,会报一个错误:
这是一个小问题,主要是因为 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
其他错误:
1、缺少服务注册
InvalidOperationException: Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Options.IConfigureOptions`1[StackExchange.Profiling.MiniProfilerOptions] Lifetime: Singleton ImplementationType: Microsoft.Extensions.DependencyInjection.MiniProfilerOptionsDefaults': Unable to resolve service for type 'Microsoft.Extensions.Caching.Memory.IMemoryCache' while attempting to activate 'Microsoft.Extensions.DependencyInjection.MiniProfilerOptionsDefaults'
直接把红色的服务注入就行了。
2、注入IMemoryCache服务
这是一个小知识点,之前也没有发现,我们使用 MiniProfiler 的时候,那些数据其实是存放到内存里的,因此用到了IMemoryCache,所以我们必须要对其进行注入,
之前2.2版本的时候,我们直接使用的是 services.AddMvc(); 这里已经包含了对内存接口的注入,但是升级3.0以后,我们可能会仅仅使用更轻量级的 services.AddControllers();这里是没有IMemoryCache服务的,所以我们必须单独注入:
services.AddSingleton<IMemoryCache>(factory => { var cache = new MemoryCache(new MemoryCacheOptions()); return cache; });
当然,你也可以直接使用 services.AddMvc();效果是一样的。
十一、其他补充中
如果你有其他的用到的,是我没有使用到的, 或者我上文没有提到的注意点,
欢迎想问提问和反馈,我会在这里,给你署名写上,让更多的小伙伴可以学会学号。
谢谢。
1、守墓老人
END、Github && Gitee
https://github.com/anjoy8/Blog.Core
https://gitee.com/laozhangIsPhi/Blog.Core
相关文章:
win server2008r2 安装.net core 安装了sdk
控制台输入dotnet --version 提示not found 看了环境变量都有
服务器重启了两次也一样,搜索到一个答案那个网友名称忘记了(实在抱歉)
环境变量C:\Program Files\dotnet\ 一定要在C:\Program Files (x86)\dotnet\;前面 自动生成的却刚好相反 (真鸡儿疼)问题解决
dotnet-hosting-3.1.0-win.exe 安装导致老项目的程序池停止
安装 vc_redist.x64.exe 最后还是停止,其他网上方法试了很多种 省略,
最后发现老项目都启用了32位应用程序,改成false就好了