.NET框架之“小馬過河”

.NET框架之“小馬過河”

有許多流行的 <code>.NET/<code>框架,大家都覺得挺“重”,認為很麻煩,重量級,不如其它“輕量級”框架,從而不願意使用。面對形形色色的框架發愁,筆者也曾發愁。但我發現只要敢於嘗試,這些框架都是“紙老虎”。就像“小馬過河”一樣,自己嘗試一下,就會發現“原來河水既不像老牛說的那樣淺,也不像松鼠說的那樣深。”

項目中的代碼,都在 <code>LINQPad6/<code>中運行並測試通過,也可以複製到<code>VisualStudio/<code>中執行。

做簡單的 <code>Http/<code>服務器很“重”

有些非常簡單的 <code>Http/<code>服務器,我看到有些<code>.NET/<code>開發居然也用<code>Node.js/<code>、<code>Python/<code>等語言,一問,他們會回答說“這種簡單的東西,用<code>.NET/<code>,太重了”。殊不知其實用<code>.NET/<code>做起來,也很輕(甚至更輕):

  1. <code>// 代碼不需要引入任何第三方包/<code>

  2. <code>var http = new HttpListener;/<code>

  3. <code>http.Prefixes.Add

    ("http://localhost:8080/");/<code>

  4. <code>http.Start;/<code>


  5. <code>while (true)/<code>

  6. <code>{/<code>

  7. <code>var ctx = await http.GetContext;/<code>

  8. <code>using var writer = new StreamWriter(ctx.Response.OutputStream);/<code>

  9. <code>writer.Write(DateTime.Now);/<code>

  10. <code>}/<code>

運行效果:

.NET框架之“小马过河”

可見,包括空行,僅10行代碼即可完成一個簡單的 <code>HTTP/<code>服務器。

使用 <code>EntityFramework/<code>很“重”

<code>EntityFramework/<code>,簡稱<code>EF/<code>,現在有兩個版本,<code>EFCore/<code>和<code>EF6/<code>,其中<code>EFCore/<code>可以同時運行在<code>.NETFramework/<code>和<code>.NETCore/<code>中,但<code>EF6/<code>只能在<code>.NETFramework/<code>中運行。本文中只測試了<code>EFCore/<code>,但<code>EF6/<code>代碼也一樣簡單。

<code>EntityFramework/<code>是<code>.NET/<code>下常用的數據訪問框架,以代碼簡單、功能強大而著名。但不少人卻嗤之以鼻、不以為意。詢問時,回答說<code>EntityFramework/<code>很“重”。

這個“重”字,我理解為它可能佔用內存高,或者它可能代碼極其麻煩,配置不方便(像<code>iBatis/<code>/<code>Hibernate/<code>那樣),真的這樣嗎?

如圖,假設我有一個 <code>UserVoiceStatus/<code>表:

.NET框架之“小马过河”

下面,我們通過 <code>EF/<code>將數據取出來:

  1. <code>// 引用NuGet包:/<code>

  2. <code>// Microsoft.EntityFrameworkCore.SqlServer/<code>

  3. <code>void Main/<code>

  4. <code>{/<code>

  5. <code>var db = new MyDB(new DbContextOptionsBuilder/<code>

  6. <code>.UseSqlServer(Util.GetPassword("ConnectionString"))/<code>

  7. <code>.Options);/<code>

  8. <code>db.UserVoiceStatus.Dump;/<code>

  9. <code>}/<code>


  10. <code>public class UserVoiceStatus/<code>

  11. <code>{/<code>

  12. <code>public byte Id { get; set; }/<code>

  13. <code>public string Name { get; set; }/<code>

  14. <code>}/<code>


  15. <code>public class MyDB : DbContext/<code>

  16. <code>{/<code>

  17. <code>public MyDB(DbContextOptions options): base(options)/<code>

  18. <code>{/<code>

  19. <code>}/<code>


  20. <code>public DbSet<uservoicestatus> UserVoiceStatus { get; set; }/<uservoicestatus>/<code>

  21. <code>}/<code>

執行效果如圖:

.NET框架之“小马过河”

注意,如果使用 <code>LINQPad/<code>,事情還能更簡單,只要一行代碼即可,效果完全一樣:<code>UserVoiceStatuses/<code>

使用 <code>ASP.NET MVC/<code>很“重”

上文說到了如何做一個簡單的 <code>Http/<code>服務器,如果想複雜一點,初始化<code>ASP.NET MVC/<code>也很簡單,甚至只需要一個文件即可完成:

  1. <code>void Main/<code>

  2. <code>{/<code>

  3. <code>WebHost/<code>

  4. <code>.CreateDefaultBuilder/<code>

  5. <code>.UseStartup<userquery>/<code>

  6. <code>.UseUrls("https://localhost:55555")/<code>

  7. <code>.Build/<code>

  8. <code>.Run;/<code>

  9. <code>}/<code>


  10. <code>public void ConfigureServices(IServiceCollection services)/<code>

  11. <code>{/<code>

  12. <code>services.AddControllers;/<code>

  13. <code>}/<code>


  14. <code>public void Configure(IApplicationBuilder app)/<code>

  15. <code>{/<code>

  16. <code>app.UseRouting;/<code>

  17. <code>app.UseEndpoints(endpoints =>/<code>

  18. <code>{/<code>

  19. <code>endpoints.MapControllerRoute(/<code>

  20. <code>name: "default",/<code>

  21. <code>pattern: "{controller}/{action}/{id?}",/<code>

  22. <code>defaults: new { controller = "Home", action = "Index" });/<code>

  23. <code>});/<code>

  24. <code>}/<code>


  25. <code>namespace Controllers/<code>

  26. <code>{/<code>

  27. <code>public class HomeController : Controller/<code>

  28. <code>{/<code>

  29. <code>public DateTime Index/<code>

  30. <code>{/<code>

  31. <code>return DateTime.Now;/<code>

  32. <code>}/<code>

  33. <code>}/<code>

  34. <code>}/<code>

麻雀雖小,五臟俱全,這麼簡短的幾千代碼中,可以使用 <code>Https/<code>、包含了依賴注入,還能完整的路由功能,就構成了<code>ASP.NET MVC/<code>的基本代碼。運行效果如圖:

.NET框架之“小马过河”

使用 <code>WebSockets/<code>很“重”

<code>WebSockets/<code>是個流行的<code>Http/<code>雙向通信技術,以前在<code>Node.js/<code>中很流行(用<code>socket.io/<code>)。代碼如下:

  1. <code>async Task Main/<code>

  2. <code>{/<code>

  3. <code>await WebHost/<code>

  4. <code>.CreateDefaultBuilder/<code>

  5. <code>.UseStartup<userquery>/<code>

  6. <code>.UseUrls("https://*:55555")/<code>

  7. <code>.Build/<code>

  8. <code>.RunAsync;/<code>

  9. <code>}/<code>


  10. <code>async Task Echo(HttpContext ctx, WebSocket webSocket, CancellationToken cancellationToken)/<code>

  11. <code>{/<code>

  12. <code>var buffer = new byte[4096];/<code>

  13. <code>ValueWebSocketReceiveResult result = await webSocket.ReceiveAsync(buffer.AsMemory, cancellationToken);/<code>

  14. <code>while (!result.EndOfMessage)/<code>

  15. <code>{/<code>

  16. <code>await webSocket.SendAsync(buffer.AsMemory(..result.Count), result.MessageType, result.EndOfMessage, cancellationToken);/<code>

  17. <code>result = await webSocket.ReceiveAsync(buffer.AsMemory, cancellationToken);/<code>

  18. <code>}/<code>

  19. <code>await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "NA", cancellationToken);/<code>

  20. <code>}/<code>


  21. <code>public void ConfigureServices(IServiceCollection services)/<code>

  22. <code>{/<code>

  23. <code>}/<code>


  24. <code>public void Configure(IApplicationBuilder app)/<code>

  25. <code>{/<code>

  26. <code>app.UseWebSockets;/<code>

  27. <code>app.Use(async (ctx, next) =>/<code>

  28. <code>{/<code>

  29. <code>if (ctx.Request.Path == "/ws")/<code>

  30. <code>{/<code>

  31. <code>if (ctx.WebSockets.IsWebSocketRequest)/<code>

  32. <code>{/<code>

  33. <code>WebSocket webSocket = await ctx.WebSockets.AcceptWebSocketAsync;/<code>

  34. <code>await Echo(ctx, webSocket, CancellationToken.None);/<code>

  35. <code>return;/<code>

  36. <code>}/<code>

  37. <code>}/<code>

  38. <code>await next;/<code>

  39. <code>});/<code>

  40. <code>app.Run(x => x.Response.WriteAsync("Please call /ws using WebSockets."));/<code>

  41. <code>}/<code>

該代碼是個 <code>Echo/<code>服務器,它會將客戶端發過來和內容,按原因返回給客戶端。然後,<code>.NET/<code>也內置了<code>WebSockets/<code>的客戶端:可以高效地訪問剛剛創建並運行的<code>WebSockets/<code>服務器。

  1. <code>using (var ws = new ClientWebSocket)/<code>

  2. <code>{/<code>

  3. <code>await ws.ConnectAsync(new Uri("wss://localhost:55555/ws"), CancellationToken.None);/<code>

  4. <code>var completeEvent = new ManualResetEventSlim;/<code>

  5. <code>var cts = new CancellationTokenSource;/<code>

  6. <code>new Task( => SendMessage(ws, cts)).Start;/<code>


  7. <code>var buffer = new byte[4096];/<code>

  8. <code>do/<code>

  9. <code>{/<code>

  10. <code>var r = await ws.ReceiveAsync(buffer, cts.Token);/<code>

  11. <code>$"[{Util.ElapsedTime}] Received {Encoding.UTF8.GetString(buffer, 0, r.Count)}".Dump;/<code>

  12. <code>} while (ws.State != WebSocketState.Closed);/<code>

  13. <code>}/<code>

  14. <code>$"[{Util.ElapsedTime}] Closed.".Dump;/<code>


  15. <code>async void SendMessage(WebSocket ws, CancellationTokenSource cts)/<code>

  16. <code>{/<code>

  17. <code>for (var i = 0; i <3; ++i)/<code>

  18. <code>{/<code>

  19. <code>await ws.SendAsync(/<code>

  20. <code>Encoding.UTF8.GetBytes($"[{Util.ElapsedTime}] Send {DateTime.Now.ToString}".Dump),/<code>

  21. <code>WebSocketMessageType.Text,/<code>

  22. <code>endOfMessage: false, default);/<code>

  23. <code>await Task.Delay(1000);/<code>

  24. <code>}/<code>

  25. <code>await ws.CloseAsync(WebSocketCloseStatus.Empty, , default);/<code>

  26. <code>cts.Cancel;/<code>

  27. <code>}/<code>

最後,客戶端與服務器雙向通信效果如下:

.NET框架之“小马过河”

使用 <code>SignalR/<code>很“重”

<code>SignalR/<code>是<code>ASP.NET/<code>推出的抽象式的<code>Http/<code>協議雙向通信框架。<code>SignalR/<code>可以用相同的<code>API/<code>,支持像長輪詢、<code>ServerSentEvents/<code>和<code>WebSocket/<code>的技術。<code>SignalR/<code>默認優先選擇使用<code>WebSocket/<code>以達到最高性能,如果客戶端或服務器不支持,則會回退至其它稍慢的技術。

<code>SignalR/<code>客戶端還支持幾乎所有語言、所有平臺。它是如此好用,幾乎可以取代傳統的請求/響應,成為新的<code>Http/<code>開發模型。(事實上<code>Blazor/<code>正在嘗試這樣做)

但 <code>SignalR/<code>最為令人震撼的,還是它非常簡單的使用方式,而恰恰是這一點給人誤會最深。它的服務端<code>API/<code>,甚至比<code>WebSocket/<code>還要簡單清晰簡單:

  1. <code>async Task Main/<code>

  2. <code>{/<code>

  3. <code>await WebHost/<code>

  4. <code>.CreateDefaultBuilder/<code>

  5. <code>.UseStartup<userquery>/<code>

  6. <code>.UseUrls("https://localhost:55555")/<code>

  7. <code>.Build/<code>

  8. <code>.RunAsync;/<code>

  9. <code>}/<code>


  10. <code>public void ConfigureServices(IServiceCollection services)/<code>

  11. <code>{/<code>

  12. <code>services.AddSignalR;/<code>

  13. <code>}/<code>


  14. <code>public void Configure(IApplicationBuilder app)/<code>

  15. <code>{/<code>

  16. <code>app.UseRouting;/<code>

  17. <code>app.UseEndpoints(endpoints =>/<code>

  18. <code>{/<code>

  19. <code>endpoints.MapHub<hubs.chathub>("/chat");/<hubs.chathub>/<code>

  20. <code>});/<code>

  21. <code>}/<code>


  22. <code>namespace Hubs/<code>

  23. <code>{/<code>

  24. <code>public class ChatHub : Hub/<code>

  25. <code>{/<code>

  26. <code>public async Task Broadcast(string id, string text)/<code>

  27. <code>{/<code>

  28. <code>await Clients.All.SendAsync("Broadcast", id, text);/<code>

  29. <code>}/<code>

  30. <code>}/<code>

  31. <code>}/<code>

前文提到, <code>SignalR/<code>提供了所有平臺的<code>SignalR/<code>客戶端,如<code>js/<code>、<code>Android/<code>等,其中當然(顯然)也包括<code>.NET/<code>的。<code>SignalR/<code>的<code>.NET/<code>客戶端使用起來也非常簡單:

  1. <code>// 引入NuGet包:Microsoft.AspNetCore.SignalR.Client/<code>

  2. <code>// 代碼在LINQPad中運行/<code>

  3. <code>var hub = new HubConnectionBuilder/<code>

  4. <code>.WithUrl("https://localhost:55555/chat")/<code>

  5. <code>.Build;/<code>


  6. <code>hub.On("Broadcast", (string id, string msg) =>/<code>

  7. <code>{/<code>

  8. <code>Console.WriteLine($"{id}: {msg}");/<code>

  9. <code>});/<code>


  10. <code>new Label("姓名: ").Dump;/<code>

  11. <code>var idBox = new TextBox(Guid.NewGuid.ToString).Dump;/<code>

  12. <code>await hub.StartAsync;/<code>

  13. <code>while (true)/<code>

  14. <code>{/<code>

  15. <code>var text = Console.ReadLine;/<code>

  16. <code>if (text == "Q") break;/<code>

  17. <code>await hub.SendAsync("Broadcast", idBox.Text, text);/<code>

  18. <code>}/<code>

這是一個非常簡單的多人聊天室,運行效果如下:

.NET框架之“小马过河”

總結

面對形形色色的框架發愁,筆者也曾發愁。但現在不了,什麼框架拿過來,馬上試試,也就幾十秒鐘的事。好用不好用,用用便知。

那麼讀者,你的“小馬過河”的故事是怎樣的呢?


分享到:


相關文章: