.NET Core 跨平台
- 2020 年 7 月 12 日
- 筆記
- dotnet core, 學習筆記

前言
.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跨平台》。