天天看點

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

緣起

1、哈喽大家中秋節(後)好呀!感覺已經好久沒有寫文章了,但是也沒有偷懶喲,我的視訊教程《系列一、NetCore 視訊教程(Blog.Core)》也已經錄制八期了,還在每周末同步更新中,歡迎大家多多指教。

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

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

 ✔ 如果要使用 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 的時候出現了問題,可以和我聊聊,我在下邊補充下,争取達到一個最全的解決方案合集。

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

好啦,廢話到此結束,馬上開始今天的遷移報告内容!🌈🌈🌈

零、NetCore3.0 有哪些新特性

從壹開始學習NetCore 44 ║ 最全的 netcore 3.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 :

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

是以,如果我們要更新 3.0 的話,就肯定要安裝指定的 SDK 了,下載下傳位址:https://dotnet.microsoft.com/download/visual-studio-sdks

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

選擇指定版本的 SDK ,然後進行安裝,最後我們就可以看到我們的本地已經安裝好了:

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

 這裡我們可以看到我們的 3.0 的 SDK 已經安裝好了,最後再做個驗證,就是在我們的 VS 2019 中,檢視是否有 3.0 的架構:

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

竟然沒有??!!别慌,這裡有兩個方法:

1、工具 -> 選項 -> 項目與解決方案 -> 右側,勾選預覽版(這個方案是2019 最舊版本的,已取消請忽略)。

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

2、在工具 -> 選項 -> 環境裡(正規是使用這個):

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

然後我們把 vs 重新啟動一下,發現已經有了:

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

安裝好了 SDK,我們就已經是成功了一半了,下邊我們就正式開始更新打怪之路。

但是這裡還有一個問題,就是打開的項目屬性裡,雖然有了 3.0 的架構,但是建立的項目,依然沒有 3.0 的部分,那這個是為什麼呢?

從壹開始學習NetCore 44 ║ 最全的 netcore 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 即可;

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

2、更新架構以及所有依賴

剛剛我們已經成功的安裝好了 3.0 的 SDK ,那接下來就是正式開始更新項目,首先呢,就是需要更新我們的目标架構,這裡有兩種方法:

第一種是直接修改我們的項目檔案 .csproj ,修改節點 <TargetFramework>netcoreapp3.0</TargetFramework>,并移除關于 Aspnetcore 2.2 相關的包;

第二種就是直接右鍵項目,屬性,應用程式,修改目标架構到 netcore 3.0 就行,就是上文截圖中顯示的那個,我個人采用的是這種方法。

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

 直接手動删除即可

記得要把項目從底層開始更新,比如從 Model 層和 Common 層開始更新,然後最後更新 API 層,就是從下向上,(這裡有個小問題,就是出現修改了,CTRL S 儲存後,又重新回到2.2了,可以重新開機下項目,重新開機下vs就行了)。

代碼修改對比圖:

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

(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 方法裡引用也要做相應的修改。

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

 (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           

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

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

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

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

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

除了上邊的 mvc 服務注冊以外,我們還需要對 UseMvc() 中間件做修改。

官方已經正式去掉了Mvc()這個短路中間件,取代他的是 .UseEndpoints() 方法,我們可以做以下修改:

從壹開始學習NetCore 44 ║ 最全的 netcore 3.0 更新實戰方案
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、注意:中間件的順序

不多說,注意下中間件的順序 。

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

三、Swagger 部分

在 netcore 3.0 中,要求我們使用的是 swagger 5.0 ,而且變化的内容也挺多的,但是原理和思路都是一樣的,大家一看就知道了,是以我就直接貼代碼了。

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

 這次的修改,主要是服務的注冊部分,中間件沒有變化,是以我們直接在 startup.cs 中的 configureService 中,做下調整:

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

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

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 = "[email protected]", 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的服務工廠:

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

2、然後在 startup.cs 檔案中,建立一個 ConfigureContainer(ContainerBuilder builder) 的方法,裡邊的内容就是我們之前寫的 Autofac 的代碼,把之前在 configureService 中的代碼都删掉。

隻不過我們這裡是注入了 builder 的執行個體對象,不用new了,然後也不用 build 容器了,交給了 hotst 幫助我們一起 build。

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

如果有任何看不懂的,請檢視我的 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();
           });
     });      

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 的版本,如果不使用的話,可能會有一個映射錯誤:

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

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

2、如果更新了以後,發現還有錯誤,一個《未将對象引用到對象的執行個體》:

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

這個時候,你可以嘗試重新生成下資料庫,好像隻需要建立下表結構就行,資料可以導入,記得做好生産環境資料庫備份。

其他還沒有發現什麼問題。

六、Authorization 部分

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

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

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

具體的代碼,在上邊講 swagger 的時候,已經粘貼完整了,你可以直接複制即可。

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

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

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

之前我的 Blog.Core 項目使用了權限過濾器公約,這樣就算 controller 沒有配置 Authorize 的話,也會預設采用這種權限過濾器,感覺很友善。

但是現在不行了,必須要在每一個 controller/action 上配置,才能在 swagger 中出現那個 小鎖 的标志,是以我又都在 controller 上,加上了 [Authorize(Permissions.Name)]

如果不配置的話,是沒有小鎖标志,也就不會啟動權限認證的作用的,隻有配置了的才有:

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

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

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

十、MiniProfiler 部分

服務注冊:

services.AddMiniProfiler(options =>
  {
      options.RouteBasePath = "/profiler";
      options.PopupRenderPosition = StackExchange.Profiling.RenderPosition.Left;
      options.PopupShowTimeWithChildren = true;
  });      

1、更新nuget包,并配合修改js調用

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

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

這是一個小問題,主要是因為 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、守墓老人

#37樓 2019-12-29 05:07 守墓老人

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就好了

END、Github && Gitee

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

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

相關文章:

  • ASP.NET Core 2.2 項目更新至 3.0 備忘錄
  • ASP.NET Core 3.0 遷移避坑指南