.NET框架之「小馬過河」
- 2019 年 10 月 3 日
- 筆記
.NET框架之「小馬過河」
有許多流行的.NET
框架,大家都覺得挺「重」,認為很麻煩,重量級,不如其它「輕量級」框架,從而不願意使用。面對形形色色的框架發愁,筆者也曾發愁。但我發現只要敢於嘗試,這些框架都是「紙老虎」。就像「小馬過河」一樣,自己嘗試一下,就會發現「原來河水既不像老牛說的那樣淺,也不像松鼠說的那樣深。」
項目中的程式碼,都在LINQPad 6
中運行並測試通過,也可以複製到Visual Studio
中執行。
做簡單的Http
伺服器很「重」
有些非常簡單的Http
伺服器,我看到有些.NET
開發居然也用Node.js
、Python
等語言,一問,他們會回答說「這種簡單的東西,用.NET
,太重了」。殊不知其實用.NET
做起來,也很輕(甚至更輕):
// 程式碼不需要引入任何第三方包 var http = new HttpListener(); http.Prefixes.Add("http://localhost:8080/"); http.Start(); while (true) { var ctx = await http.GetContext(); using var writer = new StreamWriter(ctx.Response.OutputStream); writer.Write(DateTime.Now); }
運行效果:
可見,包括空行,僅10行程式碼即可完成一個簡單的HTTP
伺服器。
使用Entity Framework
很「重」
Entity Framework
,簡稱EF
,現在有兩個版本,EF Core
和EF 6
,其中EF Core
可以同時運行在.NET Framework
和.NET Core
中,但EF 6
只能在.NET Framework
中運行。本文中只測試了EF Core
,但EF 6
程式碼也一樣簡單。
Entity Framework
是.NET
下常用的數據訪問框架,以程式碼簡單、功能強大而著名。但不少人卻嗤之以鼻、不以為意。詢問時,回答說Entity Framework
很「重」。
這個「重」字,我理解為它可能佔用記憶體高,或者它可能程式碼極其麻煩,配置不方便(像iBatis
/Hibernate
那樣),真的這樣嗎?
如圖,假設我有一個UserVoiceStatus
表:
下面,我們通過EF
將數據取出來:
// 引用NuGet包: // Microsoft.EntityFrameworkCore.SqlServer void Main() { var db = new MyDB(new DbContextOptionsBuilder() .UseSqlServer(Util.GetPassword("ConnectionString")) .Options); db.UserVoiceStatus.Dump(); } public class UserVoiceStatus { public byte Id { get; set; } public string Name { get; set; } } public class MyDB : DbContext { public MyDB(DbContextOptions options): base(options) { } public DbSet<UserVoiceStatus> UserVoiceStatus { get; set; } }
執行效果如圖:
注意,如果使用
LINQPad
,事情還能更簡單,只要一行程式碼即可,效果完全一樣:
UserVoiceStatuses
使用ASP.NET MVC
很「重」
上文說到了如何做一個簡單的Http
伺服器,如果想複雜一點,初始化ASP.NET MVC
也很簡單,甚至只需要一個文件即可完成:
void Main() { WebHost .CreateDefaultBuilder() .UseStartup<UserQuery>() .UseUrls("https://localhost:55555") .Build() .Run(); } public void ConfigureServices(IServiceCollection services) { services.AddControllers(); } public void Configure(IApplicationBuilder app) { app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller}/{action}/{id?}", defaults: new { controller = "Home", action = "Index" }); }); } namespace Controllers { public class HomeController : Controller { public DateTime Index() { return DateTime.Now; } } }
麻雀雖小,五臟俱全,這麼簡短的幾千程式碼中,可以使用Https
、包含了依賴注入,還能完整的路由功能,就構成了ASP.NET MVC
的基本程式碼。運行效果如圖:
使用WebSockets
很「重」
WebSockets
是個流行的Http
雙向通訊技術,以前在Node.js
中很流行(用socket.io
)。程式碼如下:
async Task Main() { await WebHost .CreateDefaultBuilder() .UseStartup<UserQuery>() .UseUrls("https://*:55555") .Build() .RunAsync(); } async Task Echo(HttpContext ctx, WebSocket webSocket, CancellationToken cancellationToken) { var buffer = new byte[4096]; ValueWebSocketReceiveResult result = await webSocket.ReceiveAsync(buffer.AsMemory(), cancellationToken); while (!result.EndOfMessage) { await webSocket.SendAsync(buffer.AsMemory(..result.Count), result.MessageType, result.EndOfMessage, cancellationToken); result = await webSocket.ReceiveAsync(buffer.AsMemory(), cancellationToken); } await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "NA", cancellationToken); } public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app) { app.UseWebSockets(); app.Use(async (ctx, next) => { if (ctx.Request.Path == "/ws") { if (ctx.WebSockets.IsWebSocketRequest) { WebSocket webSocket = await ctx.WebSockets.AcceptWebSocketAsync(); await Echo(ctx, webSocket, CancellationToken.None); return; } } await next(); }); app.Run(x => x.Response.WriteAsync("Please call /ws using WebSockets.")); }
該程式碼是個Echo
伺服器,它會將客戶端發過來和內容,按原因返回給客戶端。然後,.NET
也內置了WebSockets
的客戶端:可以高效地訪問剛剛創建並運行的WebSockets
伺服器。
using (var ws = new ClientWebSocket()) { await ws.ConnectAsync(new Uri("wss://localhost:55555/ws"), CancellationToken.None); var completeEvent = new ManualResetEventSlim(); var cts = new CancellationTokenSource(); new Task(() => SendMessage(ws, cts)).Start(); var buffer = new byte[4096]; do { var r = await ws.ReceiveAsync(buffer, cts.Token); $"[{Util.ElapsedTime}] Received {Encoding.UTF8.GetString(buffer, 0, r.Count)}".Dump(); } while (ws.State != WebSocketState.Closed); } $"[{Util.ElapsedTime}] Closed.".Dump(); async void SendMessage(WebSocket ws, CancellationTokenSource cts) { for (var i = 0; i < 3; ++i) { await ws.SendAsync( Encoding.UTF8.GetBytes($"[{Util.ElapsedTime}] Send {DateTime.Now.ToString()}".Dump()), WebSocketMessageType.Text, endOfMessage: false, default); await Task.Delay(1000); } await ws.CloseAsync(WebSocketCloseStatus.Empty, null, default); cts.Cancel(); }
最後,客戶端與伺服器雙向通訊效果如下:
使用SignalR
很「重」
SignalR
是ASP.NET
推出的抽象式的Http
協議雙向通訊框架。SignalR
可以用相同的API
,支援像長輪詢、Server Sent Events
和WebSocket
的技術。SignalR
默認優先選擇使用WebSocket
以達到最高性能,如果客戶端或伺服器不支援,則會回退至其它稍慢的技術。
SignalR
客戶端還支援幾乎所有語言、所有平台。它是如此好用,幾乎可以取代傳統的請求/響應,成為新的Http
開發模型。(事實上Blazor
正在嘗試這樣做)
但SignalR
最為令人震撼的,還是它非常簡單的使用方式,而恰恰是這一點給人誤會最深。它的服務端API
,甚至比WebSocket
還要簡單清晰簡單:
async Task Main() { await WebHost .CreateDefaultBuilder() .UseStartup<UserQuery>() .UseUrls("https://localhost:55555") .Build() .RunAsync(); } public void ConfigureServices(IServiceCollection services) { services.AddSignalR(); } public void Configure(IApplicationBuilder app) { app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapHub<Hubs.ChatHub>("/chat"); }); } namespace Hubs { public class ChatHub : Hub { public async Task Broadcast(string id, string text) { await Clients.All.SendAsync("Broadcast", id, text); } } }
前文提到,SignalR
提供了所有平台的SignalR
客戶端,如js
、Android
等,其中當然(顯然)也包括.NET
的。SignalR
的.NET
客戶端使用起來也非常簡單:
// 引入NuGet包:Microsoft.AspNetCore.SignalR.Client // 程式碼在LINQPad中運行 var hub = new HubConnectionBuilder() .WithUrl("https://localhost:55555/chat") .Build(); hub.On("Broadcast", (string id, string msg) => { Console.WriteLine($"{id}: {msg}"); }); new Label("姓名: ").Dump(); var idBox = new TextBox(Guid.NewGuid().ToString()).Dump(); await hub.StartAsync(); while (true) { var text = Console.ReadLine(); if (text == "Q") break; await hub.SendAsync("Broadcast", idBox.Text, text); }
這是一個非常簡單的多人聊天室,運行效果如下:
總結
面對形形色色的框架發愁,筆者也曾發愁。但現在不了,什麼框架拿過來,馬上試試,也就十幾秒鐘的事。好用不好用,用用便知。
那麼讀者,你的「小馬過河」的故事是怎樣的呢?
請關注我的微信公眾號:【DotNet騷操作】,