從壹開始學習NetCore 44 ║ 最全的 netcore 3.0 升級實戰方案

緣起

一、哈嘍你們中秋節(後)好呀!感受已經很久沒有寫文章了,可是也沒有偷懶喲,個人視頻教程《系列1、NetCore 視頻教程(Blog.Core)》也已經錄製八期了,還在每週末同步更新中,歡迎你們多多指教。javascript

二、除此以外呢,我也在平時的時間幫朋友開發了一個小項目,就是使用 .net mvc+vue+ele+mongo 框架寫的項目,以前一直想着用mvc結合着vue寫,此次也終於上手了,不過是一個小的demo,由於是朋友的項目,因此就不開源了。html

 

 ✔ 若是要使用 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 的快速發展、迭代以及接受用戶的反饋進一步修改中,咱們就能感受的到,微軟是如何的有但願而且有信心在將來的發展中,將微軟系產品進一步融入到廣大開發者的心中,咱們也要有信心微軟能作到這一點。vue

 

前言

在netcore 3.0 立刻要到來之際,我也要嚐嚐鮮,我確定不是第一個吃螃蟹的人,博客園這兩個月也是一直轟轟烈烈的進行 3.0 的更新和迭代,不過過程是怎樣的吧,至少結果目前仍是能夠的,也能夠做爲一個成功案例給你們提供一些建議和思路。java

感受嘗試就是成功的一半,因此我在中秋節這兩天,也把 Blog.Core  項目給提高到了 3.0 版本,你們如今看的個人在線地址(國外的服務,可能加載比較慢,後期會作處理 http://apk.neters.club/index.html)  就是netcore 3.0 的,整體看起來,可能沒有什麼差異,並且運行中也沒有發現任何問題(管理後臺 http://vueadmin.neters.club/),不過此次官方更新的東西仍是稍微挺多的,因此我這裏就統一作下記錄,方便你們吧,但願每個在使用 netcore 的小夥伴都能從這裏獲得一些幫助,雖然官網也有一些記錄,可是我看了看,英文的可能有些小夥伴很差理解,儘管有中文翻譯版,但是看着不是很通順,並且也不是很全,你們能夠看看:地址git

固然不只僅包括下邊的這幾點,我還在慢慢更新,若是你使用到了我 blog.core 項目中沒有用到的技術,而且本身在更新 3.0 的時候出現了問題,能夠和我聊聊,我在下邊補充下,爭取達到一個最全的解決方案合集。github

 

 

 

好啦,廢話到此結束,立刻開始今天的遷移報告內容!🌈🌈🌈web

 

零、NetCore3.0 有哪些新特性

 

 

 

netcore 1.0 到 2.0 主要的是網絡和雲服務的升級,那 net core 從2.0 到 3.0 更新的是哪些呢?redis

這裏我就簡單的列舉了下這一年來netcore 3.0 更新的比較熱門的特性,固然還有其餘的,由於本篇文章主要是講解升級實戰,因此對如下特性就不過多的鋪開講解。sql

先看看官網提到了哪些更新內容:https://docs.microsoft.com/zh-cn/aspnet/core/release-notes/aspnetcore-3.0?view=aspnetcore-3.0

 

一、性能、性能、性能,重要的地方說三遍

二、在機器學習,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#開發前端

1三、.NET Framework不支持.NET Standard 2.1

1四、IL linker

1五、發佈成單個程序 dotnet publish -r win10-x64 /p:PublishSingleFile=true

1六、And so on......

 

那下面我就針對個人 Blog.Core 項目,坐下遷移的說明,一共八個方面,不是不少,你們能夠一一對比下。

 

1、項目啓動部分

操做前必備:備份文件,這個很重要,咱們要玩兒新花樣,確定要作好備份文件,可別由於升級失敗,而很差回退。

固然個人操做是直接操做的 Blog.Core 項目,由於項目在 git 上,若是不成功,就直接回退,這種資源管理工具仍是頗有必要的。

 

一、安裝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 的框架:

 

居然沒有??!!別慌,這裏有兩個方法:

一、工具 -> 選項 -> 項目與解決方案 -> 右側,勾選預覽版(這個方案是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

 

更新:確實如今已經建立了,要注意:

一、安裝 netcore 3.0 正式版;

二、更新 vs2019 16.3.0 最新版;

三、重啓 vs2019 便可;

 

 

 

二、更新框架以及全部依賴

剛剛咱們已經成功的安裝好了 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。 

 

三、宿主機變化(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();
           });
     });

 

四、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

這個不用記憶,到時候使用的時候,會有提示的,那咱們的項目中,有哪一個地方須要修改呢,就是配置中間件的時候有一個環境變量配置須要修改下:

 

 

一、將 IHostingEnvironment env 改爲 IWebHostEnvironment env,這個時候會報錯,由於命名空間變了;

二、因此須要引用新的命名空間: using Microsoft.Extensions.Hosting;

 

到了這裏,咱們就徹底修改好了宿主機的部分,如今項目還不能正常的使用,還須要繼續修改 mvc 部分,彆着急,慢慢往下看。

 

2、MVC 部分


剛剛咱們修改了宿主機 host ,啓動項目的時候,仍是會有錯誤,主要提示咱們的中間件 .UseMvc() 已經不能被使用了,3.0後,對mvc作了較大的修改,主要從兩個方面,一個是服務註冊,一個是中間件的拆分:

一、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() 裏邊服務太多,會形成浪費,而大材小用。

代碼修改對比圖:

二、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 ,並且變化的內容也挺多的,可是原理和思路都是同樣的,你們一看就知道了,因此我就直接貼代碼了。

一、代碼修改對比圖(ConfigureServices)

 此次的修改,主要是服務的註冊部分,中間件沒有變化,因此咱們直接在 startup.cs 中的 configureService 中,作下調整:

這裏要注意下,須要引用最新的5.0兩個 Nuget 包(預覽版):Swashbuckle.AspNetCore 和 Swashbuckle.AspNetCore.Filters,這裏用的是新的類Openinfo

 

 

 

二、修改後的完整代碼

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

 

代碼中,變化比較大的地方,我已經用紅色標註,某些又作了註解,不過大部分的內容仍是和以前是同樣的,相信你們都能看得懂。

 

4、Autofac 部分

 

 關於依賴注入框架 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 依賴注入框架,已經能夠正常的使用了。

 

5、Sqlsugar 部分

隨着netcore 3.0 的更新,sqlsugar固然也要作相應的優化處理,主要是爲了配合 3.0 作處理,做者凱旋兄仍是很負責的,及時作了調整,目前 sqlsugar 的版本是 5.0.10 ,咱們若是使用 netcore 3.0 的話,就必需要使用。

一、這個 5.0.10 的版本,若是不使用的話,可能會有一個映射錯誤:

 

 

 若是遇到了這個錯誤,直接不要問,更新到最新版本就行。

 

二、若是更新了之後,發現還有錯誤,一個《未將對象引用到對象的實例》:

 

 

這個時候,你能夠嘗試從新生成下數據庫,好像只須要建立下表結構就行,數據能夠導入,記得作好生產環境數據庫備份

其餘尚未發現什麼問題。

 

6、Authorization 部分(基本解決)

 

一、swagger是如何增長校驗功能呢?

這個地方其實很簡單,剛剛在講 swagger 的時候,我也說到了,有一個地方須要咱們注意, 就是安全校驗的配置上,如今發生了變化,從服務添加變成了過濾器:

 

具體的代碼,在上邊講 swagger 的時候,已經粘貼完整了,你能夠直接複製便可。

注意必定要使用 "oauth2" 安全描述:

 

 

 

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

以前個人 Blog.Core 項目使用了權限過濾器公約,這樣就算 controller 沒有配置 Authorize 的話,也會默認採用這種權限過濾器,感受很方便。

可是如今不行了,必需要在每個 controller 上配置,才能在 swagger 中出現那個 小鎖 的標誌,因此我又都在 controller 上,加上了 [Authorize(Permissions.Name)]

若是不配置的話,是沒有小鎖標誌,也就不會啓動權限認證的做用的,只有配置了的纔有:

 

可是好像不只如此,就算是加上了特性好像也不行,這一塊我也發現有點兒問題,好像目前在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 ,能夠策略受權,也能夠角色等基本受權
             * 
             * 一、開啓公約, startup 中的全局受權過濾公約:o.Conventions.Insert(0, new GlobalRouteAuthorizeConvention());
             * 
             * 二、不開啓公約,使用 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);
        }

 

 

 

 

 

7、JSON 部分

 一、接口返回格式

 在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());

 二、中文亂碼問題

原理就很少說了,直接上代碼

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};
    
}

 

 

8、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:

一、修改服務註冊       services.AddSignalR().AddNewtonsoftJsonProtocol();
二、引用 nuget 包  Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson

 

9、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");:

 

10、MiniProfiler 部分

這裏還有一個問題,就是最新版的nuget包狀況下,會報一個錯誤:

 

這是一個小問題,主要是由於 netcore 3.0 升級之後,miniprofiler 以前版本對 System.Text.json 沒有很好的兼容致使,可是官方新版本已經更新,咱們只須要升級下版本便可:

一、升級 MiniProfiler 到最新版本 4.1.0;

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

三、保證當前 id 不能爲空,能夠本身隨意定義一個Guid值;

最終的代碼是這樣的:

<!--一、版本號要與nuget包一致;二、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

 

 

相關文章:

相關文章
相關標籤/搜索