轻量级ASP.NET Web服务与实时更新组件

在当今的软件开发领域,经常追求代码的简洁性和高效性。ASP.NET作为微软推出的一个强大的Web开发框架,其最新版本已经能够支持创建非常轻量级的Web服务。这主要得益于.NET框架的迭代更新,使得许多之前版本中常见的模板代码(boilerplate code)可以被更简洁、易于理解的配置所替代。这种配置可以舒适地放置在单个文件中,大大减少了代码量,同时不影响代码的可读性。

对于实时更新的需求,SignalR已经很好地隐藏了实现Web上实时双向通信的复杂性。DotNetify在此基础上引入了服务器和客户端之间状态管理的抽象,并且可以与多种前端框架集成,从而进一步减少了大量管道代码。

然而,对于许多Web应用程序来说,如果只是需要实现简单的用例,比如从事件源到浏览器的单向数据流,那么可能不需要服务器端的状态管理或复杂的编排。因此,类似于最小API但用于实时更新的解决方案将非常有吸引力。

接下来,将展示如何使用DotNetify.SignalR包创建一个基本的实时更新API。以下是在Program.cs文件中的实现方式:

using DotNetify; using System.Reactive.Linq; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDotNetify().AddSignalR(); var app = builder.Build(); app.MapHub<DotNetifyHub>("dotnetify"); app.MapVM("HelloWorld", () => new { Greetings = "Hello World", ServerTime = Observable.Interval(TimeSpan.FromSeconds(1)).Select(_ => DateTime.Now) }); app.Run();

新的API是MapVM,其中VM代表视图模型。第一个参数是客户端脚本用于识别连接实例的视图模型名称。第二个参数是一个匿名方法,返回一个匿名对象,表示要推送到客户端的视图模型的状态。

对象的属性值将在客户端初始连接时被序列化并包含在响应中。有趣的部分是,当属性值实现了System.IObservable时,内部逻辑将建立一个订阅,并自动将每个新值推送到客户端,只要它保持连接。

如果API无法访问依赖注入容器,那么它将没有多大用处。因此,逻辑还处理了服务注入,并支持异步操作:

app.MapVM("HelloWorld", async (IDateTimeService service) => new { ServerTime = await service.GetDateTimeObservableAsync() });

如果想让客户端能够将命令发送回服务器,这也得到了支持。将属性值设置为具有零个或一个参数的动作方法,然后可以使用客户端上的vm.$dispatch调用来调用该动作:

app.MapVM("HelloWorld", async (IDateTimeService service) => new { ServerTime = await service.GetDateTimeObservableAsync(), SetTimeZone = new Action<string>(zone => service.SetTimeZone(zone)) });

最后,这个API可以使用dotNetify的[Authorize]属性来保护,以防止未经认证的请求:

app.MapVM("HelloWorld", [Authorize] () => new { /* ... */ });

现在,已经能够创建一个非常轻量级的Web服务来提供实时更新,让将注意力转向前端。假设目标是创建一个UI组件来显示这些更新,并且它可以轻松地嵌入到现有的网站中,无论它们使用的是哪个UI框架。本着尽可能简化的精神,也希望它不需要使用Node.js来构建。

共享UI组件的最便携方式是将其制作成原生HTML自定义元素。通常,制作一个需要很多步骤,但幸运的是,从3.2版本开始,Vue提供了一个内置API,可以将Vue组件转换为一个。Vue是一个很棒的UI框架,如果只关注现代浏览器,那么使用最新的JavaScript语法编写代码是完全可能的,而不需要转译。

举了一个例子,模拟了一个基本的股票行情应用。它有一个输入字段用于查找股票符号,以及一个区域用于显示符号及其当前价格,每秒钟更新一次。以下是实现方式:

只需要添加两个前端文件到服务中:

1. index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>Stock Ticker</title> </head> <body> <stock-ticker/> <script src="https://cdn.jsdelivr.net/npm/@microsoft/signalr@5/dist/browser/signalr.min.js"></script> <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script> <script src="https://unpkg.com/dotnetify@latest/dist/dotnetify-vue.min.js"></script> <script src="/stock-ticker.js"></script> </body> </html> 2. stocker-ticker.js const StockTicker = Vue.defineCustomElement({ template: `
`, created() { this.vm = dotnetify.vue.connect("StockTicker", this) }, unmounted() { this.vm.$destroy() }, data() { return { symbol: "", StockPrices: [] } }, methods: { add() { this.vm.$dispatch({ AddSymbol: this.symbol }) this.symbol = "" }, } }) customElements.define("stock-ticker", StockTicker)
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485