.NET框架之「小馬過河」

  • 2019 年 10 月 3 日
  • 筆記

.NET框架之「小馬過河」

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

項目中的程式碼,都在LINQPad 6中運行並測試通過,也可以複製到Visual Studio中執行。

做簡單的Http伺服器很「重」

有些非常簡單的Http伺服器,我看到有些.NET開發居然也用Node.jsPython等語言,一問,他們會回答說「這種簡單的東西,用.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 CoreEF 6,其中EF Core可以同時運行在.NET Framework.NET Core中,但EF 6只能在.NET Framework中運行。本文中只測試了EF CoreEF 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很「重」

SignalRASP.NET推出的抽象式的Http協議雙向通訊框架。SignalR可以用相同的API,支援像長輪詢、Server Sent EventsWebSocket的技術。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客戶端,如jsAndroid等,其中當然(顯然)也包括.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騷操作】,
DotNet騷操作