.NET Core 跨平臺

戎"碼"一生發表於2020-07-12

前言

  .NET Core是一個開源的模組化的Framework,不管是開發web或移動裝置都在同一個Framework(.NET Core)下執行,而且 .NET Core也可在不同的作業系統上執行,包括Windows、linux、MacOS,實現了跨平臺跨裝置。
更棒的是.NET Core 在釋出程式時不用事先安裝Framework而是通過Nuget下載,這樣在初次部署時就不用安裝一個複雜而龐大Framework,而是按需下載。這種基於Nuget的按需載入鑄就.NET Core 跨平臺。

  過去總是有人會說.Net無法在linux上執行,java就可以。幾乎一提到這個問題,就不可避免的引發Java和.Net對比的口水戰。

  而.Net Core的出現,以ASP.NET的跨平臺版本出現在了我們的眼前,它順應了開源大趨勢,對.Net開發者是個喜事,也多了一個追求前沿技術的機會 。至少突破了作業系統的限制,不在侷限於windows,讓.NET 開發者和其它跨平臺語言(如java,ruby)c開發者有了更多共同的話題。

  楠木大叔,從事.NET開發近十年,具有豐富的開發實戰經驗和帶團隊經驗,經歷了數十個實戰專案中的洗禮。 深知技術的迭代迅速,深刻理解技術從業者的艱辛和不易。本教程致力於解決實際問題,以問題為導向,設定了實際工作的中的場景和案例,並逐一講授解決方案和實戰技巧。

1 .NET 生態

  2016年微軟釋出了.NET Core 1.0 迄今已有好幾年了,但是很多.NET程式設計師也一定有以下疑問:

  • .NET Core到底是不是.NET 的下一個版本?還是說只是.NET支援跨平臺的一個版本?

  • 作為傳統的.NET開發者或者說開發的程式都是在WIndows環境下面工作的,有沒有必要學習.NET Core

  • .NET Core.NET Framework有什麼不同?

  • 在開發新的程式是應該怎麼選擇.NET Core.NET Framework


  從上面圖中我們可以看到.net 主要分為三個部分.net FrameWork,.net Core,Xamarin

  • XAMARIN 主要用來構建APP的(包括IOS,Android Windows)主要用的是C#語言

  • .NET Framework這個是我們現在經常用的,用這個可以建立windows應用程式還有web applications ,現在你可以用它建立Winform ,UWP ,wpf 等等相關的應用程式 ,web 方面就是Asp.net MVC

  • .NET Core 是微軟推出的最新的開源的,跨平臺的框架,用它可以建立的應用可以執行在MAC,Linux上 。 .net core 支援UWP 和 ASP.NET Core

UWP即Windows 10 中的Universal Windows Platform簡稱。即Windows通用應用平臺,在Win 10 Mobile/Surface(Windows平板電腦)/PC/Xbox/HoloLens等平臺上執行,uwp不同於傳統pc上的exe應用也跟只適用於手機端的app有本質區別。它並不是為某一個終端而設計,而是可以在所有windows10裝置上執行。

.NET Standard

  為什麼要引入.NET Standard

  .NET生態在發展的過程中長期都是.NET Framework這條線,後面加入適用於 iOS、Android 和 Windows 的新式高效能應用程式開發的Xamarin,後續又增加了適用於 Windows、macOS 和 Linux 的.NET Core。於是.NET 生態出現了“三足鼎立”的局面。

  有三種版本的`.NET`,意味著你需要掌握三種不同的基礎類庫以寫出可以在三種平臺上執行的程式碼。

  能不能讓開發者們只需要掌握一種基礎類庫就可以適用於不同平臺,換句話說寫一份程式碼就可以在.NET Framework,.NET Core,Xamarin都能執行?而.NET Standard 的出現就解決了這個問題。.NET Standard背後的動機是在.NET生態系統中建立更大的一致性。

.NET Standard是微軟為跨平臺所規劃的.NET Framework相關平臺於系統之間的相依性標準,在此標準之下,能確保標準的應用程式介面與物件能夠跨平臺使用。.NET平臺標準是以引用元件的方式存在,其本身並沒有任何實現,真正的實現是由平臺擁有者所進行,而客戶端使用簡單的NuGet版本戳記即可獲得正確的平臺版本。

.NET Framework老專案能夠遷移到.NET Core

  我相信絕大數有一定資歷的 .NET
程式設計師都已經在.NET Framework專案中積累了大量的經驗,那麼這些專案能否直接遷移到.NET Core中呢。注意,並不是所有的.net Framework的程式碼都可以直接執行在.net core

  這是微軟一直在做的事情,也是廣大開發者的心聲。

  在 .NET 的整個歷史記錄中,它都嘗試在版本之間以及 .NET 各個風格之間保持高階別的相容性。 .NET Core 將繼續堅守這個準則。 儘管可以將 .NET Core 視為獨立於 .NET Framework 的新技術,但下面的兩個因素使 .NET Core 無法脫離 .NET Framework:

  • 有許多最初開發過或在繼續開發 .NET Framework 應用程式的開發人員。 他們希望各個 .NET 實現中的行為保持一致。

  • .NET Standard 庫專案允許開發人員建立面向 .NET Core 和 .NET Framework 共享的通用 API 的庫。 開發人員希望用於 .NET Core 應用程式的庫與用於 .NET Framework 應用程式的同一個庫的行為相同

  在希望保持各個 .NET 實現之間的相容性的同時,開發人員還希望在各個 .NET Core 版本之間保持高階別的相容性。 具體而言,為 .NET Core 早期版本編寫的程式碼應在較高版本的 .NET Core 上無縫執行。 實際上,許多開發人員都希望新發布的 .NET Core 版本中的新 API 也應該與引入這些 API 的預釋出版本相容。

從 .NET Framework 遷移到 .NET Core

  從微軟官方的表述可以看到,依然存在影響相容性的變更對新手來說,是沒有思想包袱的,但是對於老鳥,建議空杯心態,將 .NET Core 當作全新的技術來學。

2 .NET Core專案結構

結構介紹

  開啟解決方案對話方塊,展開所有的目錄,我們可以看到如下結構


  這是一個非常簡潔的結構,也是 ASP.NET Core 最基本的目錄結構,重點講一下AskBot.Web專案下的 5 個目錄和檔案

目錄/檔案 說明
依賴項 ASP.NET Core 開發、構建和執行過程中的依賴想,一般都是 NuGet 包和一些 SDK
Properties 配置,存放了一些 .json 檔案用於配置 ASP.NET Core 專案
Propertics/launchSettings.json 啟動配置檔案,為一個 ASP.NET Core 應用儲存特有的配置標準,用於應用的啟動準備工作,包括環境變數,開發埠等
wwwroot 網站根目錄,存放類似於 CSS、JS 和圖片、還有 HTML 檔案等靜態資原始檔的目錄
Program.cs 這個檔案包含了 ASP.NET Core 應用的 Main 方法,負責配置和啟動應用程式
Startup.cs Startup.cs 檔案是 ASP.NET Core 的專案的入口啟動檔案

  Program.cs 和 Startup.cs 的區別在於 Program.cs 會呼叫 Startup.cs ,這個可以通過 Program.cs 中的程式碼看出來

WebHost.CreateDefaultBuilder(args).UseStartup<Startup>();

當然了,還有很多其它的檔案,但這些檔案不是 ASP.NET Core 的必要組成部分。

實際專案中結構

以上是基本結構,在實際專案中比較流行的專案分層架構為DDD(Domain-Driven-Design)模式。目前實際專案中比較流行前後端分離模式,所以架構這塊根據實際情況來用。常規的大概如下:

3 .NET Core 執行機制

  ASP.NET Core 應用程式是在 .NET Core 控制檯程式下呼叫特定的庫,這是ASP.NET Core應用程式開發的根本變化。所有的ASP.NET託管庫都是從Program開始執行,而不是由IIS託管。也就是說 .NET工具鏈可以同時用於.NET Core 控制檯應用程式和ASP.NET Core應用程式。

public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel() //指定宿主程式為Kestrel
                .UseStartup<Startup>()// 呼叫Startup.cs類下的Configure 和 ConfigureServices
                .Build();

            host.Run();
        }

    }

Starup

  對於一個ASP.NET Core 程式而言,Startup 類是必須的。ASP.NET Core在程式啟動時會從Program類中開始執行,然後再找到UseStartup<Startup>中找到配置的Startup的類,如果不指定Startup類會導致啟動失敗。
Startup 類:

  • 可選擇性地包括 ConfigureServices 方法以配置應用的服務 。 服務是一個提供應用功能的可重用元件。 在 ConfigureServices 中註冊服務,並通過依賴關係注入 (DI) 或 ApplicationServices 在整個應用中使用服務 。
  • 包括 Configure 方法以建立應用的請求處理管道。

在應用啟動時,ASP.NET Core 執行時會呼叫 ConfigureServices 和 Configure:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

ConfigureServices 方法

  ConfigureServices方法是應用程式執行時將服務新增到容器中,其實就是註冊ASP.NET Core中Configure方法、中介軟體及Controller等地方需要用到的依賴注入關係,用ASP.NET Core專案模板的時候預設會將MVC的服務新增到容器中

ConfigureServices 方法:

  • 可選。
  • 在 Configure 方法配置應用服務之前,由主機呼叫。
  • 其中按常規設定配置選項。

IServiceCollection 上有 Add{Service} 擴充套件方法。 例如,AddDbContext、AddDefaultIdentity、AddEntityFrameworkStores 和 AddRazorPages:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
        services.AddDefaultIdentity<IdentityUser>(
            options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddRazorPages();
    }

將服務新增到服務容器,使其在應用和 Configure 方法中可用。

Configure

  Configure 方法用於指定應用響應 HTTP 請求的方式。 可通過將中介軟體元件新增到 IApplicationBuilder 例項來配置請求管道。 Configure 方法可使用 IApplicationBuilder,但未在服務容器中註冊。 託管建立 IApplicationBuilder 並將其直接傳遞到 Configure。

 public class Startup
    {

        public Startup(IConfiguration configuration)
        {

            Configuration = configuration;

        }

        public IConfiguration Configuration { get; }


        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
            
            app.UseStaticFiles();
            
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

    }

  ASP.NET Core是通過對IApplicationBuilder進行擴充套件來構建中介軟體的, 上面程式碼中每個use擴充套件方法都是將中介軟體新增到請求管道。也可以給Configure方法附加服務(如:IHostingEnvironment)這些服務在ConfigureServices方法中被初始化。

  用ASP.NET Core專案模板新增的應用程式,預設新增的幾個中介軟體:

  • UseStaticFiles 允許應用程式提供靜態資源。
  • UseMvc 將MVC新增到管道並允許配置路由。

4 自定義全域性異常處理

4.1 Net Core中使用中介軟體方式

  首先,建立一箇中介軟體ExceptionMiddleware

public class ExceptionMiddleware
    {
        private readonly RequestDelegate next;
        private IHostingEnvironment environment;

        public ExceptionMiddleware(RequestDelegate next,IHostingEnvironment environment)
        {
            this.next = next;
            this.environment = environment;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await next.Invoke(context);
                var features = context.Features;
            }
            catch (Exception e)
            {
                await HandleException(context, e);
            }
        }

        private async Task HandleException(HttpContext context, Exception e)
        {
            context.Response.StatusCode = 500;
            context.Response.ContentType = "text/json;charset=utf-8;";
            string error = "";

            if (environment.IsDevelopment())
            {
                var json = new { message = e.Message};
                error = JsonConvert.SerializeObject(json);
            }
            else
                error = "抱歉,出錯了";

            await context.Response.WriteAsync(error);
        }
    }

  建立 HandleException(HttpContext context, Exception e) 處理異常,判斷是 Development 環境下,輸出詳細的錯誤資訊,非 Development 環境僅提示呼叫者“抱歉,出錯了”,同時使用 NLog 元件將日誌寫入硬碟;同樣,在 Startup.cs 中將 ExceptionMiddleware 加入管道中

//ExceptionMiddleware 加入管道
app.UseMiddleware<ExceptionMiddleware>();

啟動除錯,結果如下

{"message":"Attempted to divide by zero."}

  統一封裝冷異常處理方式和訊息格式,對前端也很友好。

4.2 使用ExceptionFilter

  前面提到,過濾器可以處理錯誤異常。這裡可以實踐一把。

  新建一個.NET Core MVC控制器(.net core WebAPI也類似)。

  我在Test/Index Action方法中故意製造一個異常(我們知道在被除數不能為0).

public IActionResult Index()
{
     int a = 0, b = 5;
     var result = b/a;
}

  在Visual Studio中除錯報錯了
使用場景

  我們深知,異常這樣報錯很不友好,於是我們用了萬能的try-catch

public IActionResult Index()
{
      try
        {
            int a = 0, b = 5;
            var result = b / a;
        } catch (Exception)
        {
            throw new ArgumentException("被除數不能為0", "a");
        }
}

  這樣異常提示確實友好了,並且我們攔截了異常,甚至可以將異常記錄到日誌中。

使用場景

  但是每個方法都這樣加會不會覺得很煩?有沒有想過一勞永逸的辦法。從架構層面應該這樣思考。

  在傳統的 Asp.Net MVC 應用程式中,我們一般都使用服務過濾的方式去捕獲和處理異常,這種方式 非常常見,而且可用性來說,體驗也不錯,幸運的是 Asp.Net Core也完整的支援該方式。 新建一個全域性異常過濾器GlobalExceptionFilter.cs,繼承自IExceptionFilter。

public class GlobalExceptionFilter:Attribute, IExceptionFilter
    {
        private readonly IHostingEnvironment _hostingEnvironment;
        private readonly IModelMetadataProvider _modelMetadataProvider;

        public GlobalExceptionFilter(
            IHostingEnvironment hostingEnvironment,
            IModelMetadataProvider modelMetadataProvider)
        {
            _hostingEnvironment = hostingEnvironment;
            _modelMetadataProvider = modelMetadataProvider;
        }
        /// <summary>
        /// 發生異常進入
        /// </summary>
        /// <param name="context"></param>
        public async void OnException(ExceptionContext context)
        {
            ContentResult result = new ContentResult
            {
                StatusCode = 500,
                ContentType = "text/json;charset=utf-8;"
            };

            if (_hostingEnvironment.IsDevelopment())
            {
                var json = new { message = context.Exception.Message };
                result.Content = JsonConvert.SerializeObject(json);
            }
            else
            {
                result.Content = "抱歉,出錯了";
            }
            context.Result = result;
            context.ExceptionHandled = true;
        }
    }

  我們在startup.cs中進行中注入

// 將異常過濾器注入到容器中
services.AddScoped<GlobalExceptionFilter>();

  然後在需要的控制器上加上特性**ServiceFilter(typeof(GlobalExceptionFilter))]

 [ServiceFilter(typeof(GlobalExceptionFilter))]
 public class TestController : Controller

  啟動程式,錯誤提示,頁面只會顯示單純錯誤資訊。

{"message":"Attempted to divide by zero."}

5 .NET Core過濾器(Filter)

  幹了多年開發越來越覺得,異常處理和定位的能力反映出開發者硬核能力。對每一次線bug排查的覆盤和總結,能讓你功力倍增。
在日常開發中,除了考慮程式碼的嚴謹性,還要重視日子的記錄。

過濾器

  通過使用ASP.NET Core 中的篩選器,可在請求處理管道中的特定階段之前或之後執行程式碼。

  內建過濾器處理任務,例如:

  • 授權(防止使用者訪問未獲授權的資源)。
  • 響應快取(對請求管道進行短路出路,以便返回快取的響應)。

  可以建立自定義過濾器,用於處理橫切關注點。 橫切關注點的示例包括錯誤處理、快取、配置、授權和日誌記錄。 過濾器可以避免複製程式碼。 例如,錯誤處理異常過濾器可以合併錯誤處理。

過濾器的工作原理

  過濾器在 ASP.NET Core 操作呼叫管道(有時稱過濾器管道)內執行。 過濾器管道在 ASP.NET Core 選擇了要執行的操作之後執行。

使用場景

過濾器型別

  熟悉.NET MVC框架的同學應該知道,MVC也提供了5大過濾器供我們用來處理請求前後需要執行的程式碼。分別是授權過濾器(AuthenticationFilter),資源過濾器(resource-filters),操作過濾器(ActionFilter),異常過濾器(ExceptionFilter),結果過濾器(ResultFilter)。

  每種過濾選器型別都過濾器管道中的不同階段執行:

  • 授權過濾器最先執行,用於確定是否已針對請求為使用者授權。 如果請求未獲授權,授權過濾器可以讓管道短路。

  • 資源過濾器

    • 授權後執行。
    • OnResourceExecuting 在過濾器管道的其餘階段之前執行程式碼。 例如,OnResourceExecuting 在模型繫結之前執行程式碼。
    • OnResourceExecuted 在管道的其餘階段完成之後執行程式碼。
  • 操作過濾器

    • 在呼叫操作方法之前和之後立即執行程式碼。
    • 可以更改傳遞到操作中的引數。
    • 可以更改從操作返回的結果。
    • 不可在 Razor Pages 中使用。
  • 異常過濾器在向響應正文寫入任何內容之前,對未經處理的異常應用全域性策略。
    結果過濾器在執行操作結果之前和之後立即執行程式碼。 僅當操作方法成功執行時,它們才會執行。 對於必須圍繞檢視或格式化程式的執行的邏輯,它們很有用。

  下圖展示過濾器型別在篩選器管道中的互動方式。
使用場景

過濾器使用

  在.net core 中,一般是在StartUp.cs的ConfigureServices方法中註冊

// 將異常過濾器注入到容器中
services.AddScoped<GlobalExceptionFilter>();

6 總結

  在日常工作中,我們應用dotNetcore的專案其實蠻多的,支援跨平臺真的部署,無疑是給更多C# 程式設計師一個突破自身瓶頸,開闊視野,接納多系統,多語言的機會。
更多教程可以檢視《dotnet跨平臺》

參考

相關文章