.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框架之“小马过河”

总结

面对形形色色的框架发愁,笔者也曾发愁。但现在不了,什么框架拿过来,马上试试,也就几十秒钟的事。好用不好用,用用便知。

那么读者,你的“小马过河”的故事是怎样的呢?


分享到:


相關文章: