为.NET引入Minimal Real-Time API

目录

介绍

Program.cs

示例:最小的实时Web组件

1. index.html

3. StockTicker.csproj

4. Program.cs

5. StockTickerService.cs


介绍

.NET 6去年底问世时,我对现在编写ASP.NET Web服务的轻量级感到非常惊讶。您通常会在.NET的先前迭代中找到的大部分样板代码都可以替换为这种简洁、易于理解的配置,该配置可以轻松地放入单个文件中。作为一个喜欢在可以做同样的事情并且不影响可读性的情况下减少编写代码的人,这引起了我的共鸣。

我受到启发,做了一些与实时更新类似的事情。SignalR在隐藏在网络上实现实时双向通信的所有复杂性方面已经做得很好。DotNetify通过在服务器及其连接的客户端之间引入状态管理抽象来构建它,该抽象与各种前端框架集成,并且这样做可以减少大量管道代码。

然而,当谈到利用实时技术的Web应用程序时,我怀疑对于许多应用程序来说,用例相当简单:从事件源到浏览器的单向数据流。不需要服务器端状态管理或复杂的编排。像最小的API但用于实时更新的东西会很有吸引力。

因此,事不宜迟,这就是它最基本的形式(需要依赖DotNetify.SignalR包):

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();

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

当客户端最初连接时,对象的属性值将被序列化并包含在响应中。这里有趣的部分是属性值何时实现System.IObservable<T>。只要客户端保持连接,内部逻辑就会建立订阅并自动将每个新值推送给客户端。

如果无法访问依赖注入容器,此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));
});

最后,可以使用dotNetify的[Authorize]属性保护此AP免受未经身份验证的请求:

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

示例:最小的实时Web组件

现在,提供实时更新的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: `
    <form onsubmit="return false">
      <div>
        <input type="text" placeholder="Enter symbol" v-model="symbol"/>
        <button type="submit" @click="add">Add</button>
      </div>
    </form>
    <div v-for="(price, symbol) in StockPrices" :key="symbol">
      <h5>{{ symbol }}</h5>
      <h1>{{ price }}</h1>
    </div>
`,
  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)

以及此示例Web服务中的其余文件。

3. StockTicker.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="DotNetify.SignalR" Version="5.3.0" />
  </ItemGroup>

</Project>

4. Program.cs

using DotNetify;
using StockTicker;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDotNetify().AddSignalR();
builder.Services.AddScoped<IStockTickerService, StockTickerService>();

var app = builder.Build();
app.MapHub<DotNetifyHub>("/dotnetify");
app.MapVM("StockTicker", (IStockTickerService service) => new
{
   service.StockPrices,
   AddSymbol = new Action<string>(symbol => service.AddSymbol(symbol))
});
app.UseStaticFiles();
app.MapFallbackToFile("index.html");

app.Run();

5. StockTickerService.cs

using System.Reactive.Subjects;
using System.Reactive.Linq;
using StockPriceDict = System.Collections.Generic.Dictionary<string, double>;

namespace StockTicker;

public interface IStockTickerService
{
   IObservable<StockPriceDict> StockPrices { get; }
   void AddSymbol(string symbol);
}

public class StockTickerService : IStockTickerService
{
   private readonly Subject<StockPriceDict> _stockPrices = new();
   private readonly List<string> _symbols = new();
   private readonly Random _random = new();

   public IObservable<StockPriceDict> StockPrices => _stockPrices;

   public StockTickerService()
   {
      Observable.Interval(TimeSpan.FromSeconds(1))
         .Select(_ => _symbols
            .Select(x => KeyValuePair
              .Create(x, Math.Round(1000 * _random.NextDouble(), 2)))
            .ToDictionary(x => x.Key, y => y.Value))
         .Subscribe(_stockPrices);
   }

   public void AddSymbol(string symbol)
   {
      if (!_symbols.Contains(symbol))
         _symbols.Add(symbol);
   }
}

我不了解你,但是,使用几个小文件而没有巨大的node_modules来启动和运行它的感觉真的很好!

https://www.codeproject.com/Articles/5323324/Introducing-Minimal-Real-Time-API-for-NET

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值