一、哈喽你们中秋节(后)好呀!感受已经很久没有写文章了,可是也没有偷懒哟,个人视频教程《系列1、NetCore 视频教程(Blog.Core)》也已经录制八期了,还在每周末同步更新中,欢迎你们多多指教。javascript
二、除此以外呢,我也在平时的时间帮朋友开发了一个小项目,就是使用 .net mvc+vue+ele+mongo 框架写的项目,以前一直想着用mvc结合着vue写,此次也终于上手了,不过是一个小的demo,由于是朋友的项目,因此就不开源了。html
言归正传,👉 从2018年8月就开始据说 netcore 要准备3.0了,👉 到了近期 v3.0.0-preview9 的发布,官方也最终定稿不会再更新了, 👉 接着立刻 在下周 9月23日至25日 .NET Conf 社区大会上,会正式推出 netcore3.0 版本, (最后 👉 微软会将 .netcore 和 .net 进一步融合,推出完美跨平台 net 5.0 版本,这里暂时先不说),单单从这一年里 netcore 3.0 的快速发展、迭代以及接受用户的反馈进一步修改中,咱们就能感受的到,微软是如何的有但愿而且有信心在将来的发展中,将微软系产品进一步融入到广大开发者的心中,咱们也要有信心微软能作到这一点。前端
在netcore 3.0 立刻要到来之际,我也要尝尝鲜,我确定不是第一个吃螃蟹的人,博客园这两个月也是一直轰轰烈烈的进行 3.0 的更新和迭代,不过过程是怎样的吧,至少结果目前仍是能够的,也能够做为一个成功案例给你们提供一些建议和思路。vue
感受尝试就是成功的一半,因此我在中秋节这两天,也把 Blog.Core 项目给提高到了 3.0 版本,你们如今看的个人在线地址(http://apk.neters.club/index.html) 就是netcore 3.0 的,整体看起来,可能没有什么差异,并且运行中也没有发现任何问题,(具体的请看个人后台项目:http://vueadmin.neters.club/),不过此次官方更新的东西仍是稍微挺多的,因此我这里就统一作下记录,方便你们吧,但愿每个在使用 netcore 的小伙伴都能从这里获得一些帮助,虽然官网也有一些记录,可是我看了看,英文的可能有些小伙伴很差理解,尽管有中文翻译版,但是看着不是很通顺,并且也不是很全,你们能够看看:地址。java
固然不只仅包括下边的这几点,我还在慢慢更新,若是你使用到了我 blog.core 项目中没有用到的技术,而且本身在更新 3.0 的时候出现了问题,能够和我聊聊,我在下边补充下,争取达到一个最全的解决方案合集。git
好啦,废话到此结束,立刻开始今天的迁移报告内容!🌈🌈🌈github
netcore 1.0 到 2.0 主要的是网络和云服务的升级,那 net core 从2.0 到 3.0 更新的是哪些呢?web
这里我就简单的列举了下这一年来netcore 3.0 更新的比较热门的特性,固然还有其余的,由于本篇文章主要是讲解升级实战,因此对如下特性就不过多的铺开讲解。redis
性能、性能、性能,重要的地方说三遍sql
在机器学习,AI等很好的支持
对Winform、WPF的支持
gRPC的添加
支持 API 受权在单页面应用 (Spa) 中提供身份验证、实现 Open ID Connect 的IdentityServer结合。
Worker Service 模板,为开发作服务或监控微服务相关Bus
Microsoft.Data.SqlClient:独立存在于.NET Framework和.NET Core中
ReadyToRun
HttpClient支持HTTP/2
Json.NET 不在内置在框架内,使用System.Text.Json
HostBuilder 替换掉WebHostBuilder
Blazor 是一个用于使用 .NET 生成交互式客户端 Web UI 的框架,用c#开发前端
.NET Framework不支持.NET Standard 2.1
IL linker
发布成单个程序 dotnet publish -r win10-x64 /p:PublishSingleFile=true
那下面我就针对个人 Blog.Core 项目,坐下迁移的说明,一共八个方面,不是不少,你们能够一一对比下。
操做前必备:备份文件,这个很重要,咱们要玩儿新花样,确定要作好备份文件,可别由于升级失败,而很差回退。
固然个人操做是直接操做的 Blog.Core 项目,由于项目在 git 上,若是不成功,就直接回退,这种资源管理工具仍是颇有必要的。
首先能够查看本身的本地 SDK 是什么版本的,好比个人目前只有 2.1和 2.2 :
因此,若是咱们要升级 3.0 的话,就确定要安装指定的 SDK 了,下载地址:https://dotnet.microsoft.com/download/visual-studio-sdks
选择指定版本的 SDK ,而后进行安装,最后咱们就能够看到咱们的本地已经安装好了:
这里咱们能够看到咱们的 3.0 的 SDK 已经安装好了,最后再作个验证,就是在咱们的 VS 2019 中,查看是否有 3.0 的框架:
居然没有??!!别慌,这里有两个方法:
一、工具 -> 选项 -> 项目与解决方案 -> 右侧,勾选预览版(这个方案是2019 最旧版本的,已取消请忽略)。
二、在工具 -> 选项 -> 环境里(正规是使用这个):
而后咱们把 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
刚刚咱们已经成功的安装好了 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包都更新到最新的版本,由于有些是为了迎接 netcore 3.0,作了相应的修改,好比下午说到的 swagger ,必定要更新到 5.0+版本。
到了这里,咱们的项目已经把框架和依赖升级完成了,是否是很简单,从新编译,运行,这里确定会有错误,别着急,接下来咱们就进一步修改 Code 中出现的bug。
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(); }); });
从上边咱们也能够看得出来,官方更新了 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
这个不用记忆,到时候使用的时候,会有提示的,那咱们的项目中,有哪一个地方须要修改呢,就是配置中间件的时候有一个环境变量配置须要修改下:
一、将 IHostingEnvironment env 改为 IWebHostEnvironment env,这个时候会报错,由于命名空间变了;
二、因此须要引用新的命名空间: using Microsoft.Extensions.Hosting;
到了这里,咱们就彻底修改好了宿主机的部分,如今项目还不能正常的使用,还须要继续修改 mvc 部分,别着急,慢慢往下看。
刚刚咱们修改了宿主机 host ,启动项目的时候,仍是会有错误,主要提示咱们的中间件 .UseMvc() 已经不能被使用了,3.0后,对mvc作了较大的修改,主要从两个方面,一个是服务注册,一个是中间件的拆分:
在 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() 里边服务太多,会形成浪费,而大材小用。
代码修改对比图:
除了上边的 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?}"); });
那是否是到了这里已经完成了呢,答案固然是否认的,咱们的项目不可能这么简单,确定还会有其余的依赖,还有各类各样的中间件,那咱们在升级的过程当中,还会有哪些地方须要作处理呢,就好比下边的这些。
在 netcore 3.0 中,要求咱们使用的是 swagger 5.0 ,并且变化的内容也挺多的,可是原理和思路都是同样的,你们一看就知道了,因此我就直接贴代码了。
此次的修改,主要是服务的注册部分,中间件没有变化,因此咱们直接在 startup.cs 中的 configureService 中,作下调整:
这里要注意下,须要引用两个 Nuget 包:Swashbuckle.AspNetCore 和 Swashbuckle.AspNetCore.Filters
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 的变化,总体来讲不是很大,主要是在依赖容器的使用上,在 2.2 的时候,咱们是直接修改的的 ConfigureServices ,而后将容器实例给 return 出去,可是 3.0 以后,ConfigureServices 不能是返回类型了,只能是 void 方法,那咱们就不用 return 出去了,官方给咱们提供了一个服务提供上工厂,咱们从这个工厂里拿,而不是将服务配置 return 出去。
一、首先咱们须要在 Program.cs 中的 CreateHostBuilder 中,添加Autofac的服务工厂:
二、而后在 startup.cs 文件中,新建一个 ConfigureContainer(ContainerBuilder builder) 的方法,里边的内容就是咱们以前写的 Autofac 的代码,把以前在 configureService 中的代码都删掉。
只不过咱们这里是注入了 builder 的实例对象,不用new了,而后也不用 build 容器了,交给了 hotst 帮助咱们一块儿 build。
若是有任何看不懂的,请查看个人 Blog.Core 项目中的代码。
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(); }); });
就是将咱们的Autofac的容器,从 configureService 中,转向了咱们的宿主机中了,步骤是:
一、删除 ConfigureService 中的全部 Autofac 配置内容;
二、将刚刚删除的配置内容,拷贝到新建一个 ConfigureContainer 方法中;
三、在 ConfigureContainer 方法中,不要进行 build 操做,而后 Main 入口方法中的 Build() 去执行。
四、在 Program.cs 的 CreateHostBuilder 中,新增服务工厂实例。
好了,到如今,咱们能够尝试看看 Autofac 依赖注入框架,已经能够正常的使用了。
随着netcore 3.0 的更新,sqlsugar固然也要作相应的优化处理,主要是为了配合 3.0 作处理,做者凯旋兄仍是很负责的,及时作了调整,目前 sqlsugar 的版本是 5.0.9 ,咱们若是使用 netcore 3.0 的话,就必需要使用。
一、这个 5.0.9 的版本,若是不使用的话,可能会有一个映射错误:
若是遇到了这个错误,直接不要问,更新到最新版本就行。
二、若是更新了之后,发现还有错误,一个《未将对象引用到对象的实例》:
这个时候,你能够尝试从新生成下数据库,好像只须要建立下表结构就行,数据能够导入,记得作好生产环境数据库备份。
其余尚未发现什么问题。
这一块我也发现有点儿问题,好像目前在controller 上配置 [Authorize(Policy = Permissions.Name)] 并不能实现受权的目的,就算是用官网的在短路节点配置也不行, .RequireAuthorization(new AuthorizeAttribute() { Policy = Permissions.Name, });,因此,起做用的仍是个人受权公约过滤器,并非加的特性,我还在调试,若是你正好写到 netcore 3.0 受权策略了,请评论,不胜感激。
这个地方其实很简单,刚刚在将 swagger 的时候,我也说到了,有一个地方须要咱们注意, 就是安全校验的配置上,如今发生了变化,从服务添加变成了过滤器:
以前个人 Blog.Core 项目使用了权限过滤器公约,这样就算 controller 没有配置 Authorize 的话,也会默认采用这种权限过滤器,
可是如今不行了,必需要在每个 controller 上配置,才能在 swagger 中出现那个 小锁 的标志,因此我又都在 controller 上,加上了 [Authorize(Permissions.Name)]
若是不配置的话,是没有小锁标志,也就不会启动权限认证的做用的,只有配置了的才有,不只如此,你们也能够看到,在左侧已经把该接口对应的权限也写上了:
在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,具体的使用方法请看:
一、若是使用 .net core 3.0 内置的 System.Text.Json ,配置方法以下:
services.AddMvc().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
options.JsonSerializerOptions.PropertyNamingPolicy = null; });
二、若是使用 Newtonsoft.Json ,配置方法以下:
services.AddControllers()
.AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
这个很简单,官方中间件取消了 UseSignalR 中间件,而放到了 UseEndpoints 短路中间件中,配置以下:
app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapHub<ChatHub>("/api2/chatHub"); endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); });
CORS变化其实不大,总体来讲和 2.2 同样的,具体的按照 以前的写法来写就行。
只是已经不支持向全部域名开放了,因此下边的 Policy 能够删除了,其余的不用变化:
c.AddPolicy("AllRequests", policy => { policy .AllowAnyOrigin()//容许任何源
.AllowAnyMethod()//容许任何方式
.AllowAnyHeader()//容许任何头
.AllowCredentials();//容许cookie
});
而后就是要注意中间件的顺序,这里记得还要带上 policy 的名称 ,仍是 app.UseCors("LimitRequests");:
其余补充中
若是你有其余的用到的,是我没有使用到的, 或者我上文没有提到的注意点,
欢迎想问提问和反馈,我会在这里,给你署名写上,让更多的小伙伴能够学会学号。
谢谢。
https://github.com/anjoy8/Blog.Core
https://gitee.com/laozhangIsPhi/Blog.Core
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?}"); });