.NET框架之“小马过河”

.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 { get; set; }
}

执行效果如图:

注意,如果使用 LINQPad ,事情还能更简单,只要一行代码即可,效果完全一样:

UserVoiceStatuses

使用 ASP.NET MVC 很“重”

上文说到了如何做一个简单的 Http 服务器,如果想复杂一点,初始化 ASP.NET MVC 也很简单,甚至只需要一个文件即可完成:

void Main()
{
    WebHost
        .CreateDefaultBuilder()
        .UseStartup()
        .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()
        .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()
        .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("/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骚操作】,