65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 SignalR 上的 MVVM 模式简化实时 Web 应用程序 - 第 1 部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (19投票s)

2015 年 12 月 10 日

Apache

6分钟阅读

viewsIcon

46452

SignalR 非常适合构建实时 Web 功能。MVVM 非常适合开发前端。如果它们可以一起使用会怎样?本技巧将展示如何做到这一点,以一个简单的项目为例,在 Web 浏览器上制作实时图表。

引言

关于 MVVM 模式的文章已经很多了,这是理所当然的,因为它带来了显著的优势,使您的应用程序更容易开发、测试和维护。然后是 SignalR,一个 .NET 库,它将 Web 上复杂的推送技术机制抽象为简单的函数调用。

我编写了一个轻量级的 C#/.NET/JavaScript 库,并将其发布为一个免费的开源项目,名为 dotNetify,它将这两者结合起来。前提很简单——我希望将我的 Web 应用程序的前端架构设计为由视图和视图模型组成,并且它们通过自动化、声明式绑定进行通信,这是 MVVM 的标志。当然,已经有基于 JavaScript 的 Web 框架可以做到这一点,但关键是:我希望视图模型在服务器上,用 .NET 编写。为什么?好吧,

  • 我不想编写服务层及其 AJAX 调用和 RESTful Web API,只是为了来回传输数据——这是一项繁琐的工作,如果自动化绑定可以以同样高的带宽效率为我完成这项工作,那不是很好吗?
  • 我使用 C#/.NET 已经非常高效,有些表示逻辑问题我可以轻松优雅地用 .NET 解决,但如果我使用那些 JavaScript 框架,我知道这会给我带来很多麻烦。

当然,挑战在于开发这种自动化、双向绑定,它可以在 Web 上工作,并且只调度更新以提高带宽效率,这就是 SignalR 的用武之地。它已经能够促进 Web 上的双向通信,所以我只需要在其之上构建一个 MVVM 风格的抽象。

另一个优秀的开源库 Knockout(更新:也支持 React!) 为我提供了在 HTML 视图上进行声明式绑定的语法和机制。Knockout 有在浏览器上运行的视图模型的概念,所以我做了对我来说最合乎逻辑的事情

  • 从我编写的 .NET 视图模型自动生成客户端视图模型
  • 让 Knockout 促进 HTML 视图和自动生成的客户端视图模型之间的绑定
  • 并让 SignalR 促进客户端视图模型与服务器端 .NET 视图模型之间的绑定

现在,我的 HTML 视图和 .NET 视图模型之间实现了端到端的自动化绑定;太棒了!

我添加的另一个部分是提供在客户端注入 JavaScript 代码的功能,以允许我完全控制此通信过程。我称之为“代码隐藏”。外面有很多很棒的 JavaScript UI 组件,但当然它们不提供可绑定属性——所以我需要代码隐藏在幕后工作,以促进我在 HTML 视图上声明的绑定。

所以,我想我已经把所有拼图都组装好了。为了演示其效果,我编写了一个简单的应用程序,在浏览器上显示一个图表,每秒更新一次数据。

实时图表 Web 应用程序

Github 存储库中的源代码只是一个基本的 ASP.NET MVC 解决方案,它是从 Visual Studio 2015 模板创建的(也兼容 VS 2013)。我打包了 dotNetify 库并将其发布到 Nuget.org,因此当您编译它时,它会首先拉取库文件及其所有依赖项。

现在我们来谈谈视图模型。这是一个与 UI 无关的视图抽象,我将在其中编写我的表示逻辑。对于实时图表应用程序,视图模型非常简单

ViewModel

public class LiveChartVM : BaseVM
{
   private Timer _timer = new Timer(1000);
   private Random _random = new Random();

   public double[] Data
   {
      get { return Get<double[]>(); }
      set { Set(value); }
   }

   public LiveChartVM()
   {
      // Create initial data for the chart.
      Data = new double[20];
      for (int i = 0; i < 20; i++)
         Data[i] = _random.Next(1, 100);

      _timer.Elapsed += Timer_Elapsed;
      _timer.Start();
   }

   public override void Dispose()
   {
      _timer.Stop();
      _timer.Elapsed -= Timer_Elapsed;
      base.Dispose();
   }

   private void Timer_Elapsed(object sender, ElapsedEventArgs e)
   {
      Data = new double[] { _random.Next(1, 100) };
      PushUpdates();
   }
}

只有一个名为 Data 的属性来为图表提供数据——一个将绑定到视图的双精度浮点数数组,并且有一个基本的 Windows 计时器来模拟每 1 秒更新一次。注意视图模型继承自 BaseVM。这是 dotNetify 库中的基类,它隐藏了我们之前讨论的所有细节。

BaseVM 类提供自动属性方法 GetSet,它们实现了 INotifyPropertyChanged 以支持绑定机制。它提供 PushUpdates 方法,顾名思义:将更新推送到客户端。如果您认为为什么这不能在 Data 属性值更新时自动完成,我的理由是优化:当有多个活动视图模型时,此方法会将它们全部池化,以便批量处理更新,而无需多次昂贵的调用。

接下来是视图;它也极其简单

视图

<html>
   <head>
      <script src="/Scripts/require.js" data-main="/Scripts/app"></script>
      <script src="/Scripts/Example/chart.min.js" type="text/javascript"></script>
      <script src="/Scripts/Example/LiveChart.js" type="text/javascript"></script>
   </head>
   <body>
         <h3>Live Chart</h3>
         <div data-vm="LiveChartVM">
            <canvas data-bind="vmOn: { Data: updateChart }" />
         </div>
   </body>
</html>

第一个 script 标签使用 RequireJS 模块加载器库加载 dotNetify JavaScript 库及其所有依赖项。第二个 script 标签加载 ChartJS 库,它提供 HTML5 画布元素上的图表渲染,最后一个是我们稍后将讨论的“代码隐藏”。

注意“data-vm”属性。这是 dotNetify 定义范围的方式:基本上,它内部的 HTML 元素将属于指定视图模型的管辖范围;在本例中,它是 LiveChartVM,我们之前定义的视图模型。

data-bind”属性来自 Knockout,这是它在 HTML 元素上声明绑定的方式。它最棒的地方在于 Knockout 也支持自定义绑定,我充分利用了这一点。ChartJS 自然不提供可绑定属性来填充其数据,所以我编写了一个名为“vmOn”的自定义绑定,它接受一个对象字面量格式的 2 个参数:要绑定的视图模型属性,以及当该属性值更改时要调用的“代码隐藏”方法。

这就引出了代码隐藏。在一个完美的世界里,一切都可以声明式绑定,我就不必编写它。但我没有生活在那个世界里,所以它来了

代码隐藏

var LiveChartVM = (function () {
   return {
      // On data update, update the chart.
      updateChart: function (iItem, iElement) {
         var vm = this;
         var data = vm.Data();

         if (vm._chart == null) {
            vm._chart = this.createChart(data, iElement);
            vm._counter = data.length;
         }
         else {
            for (var i = 0; i < data.length; i++) {
               vm._chart.addData([data[i]], vm._counter++);
               // Remove the oldest data.
               vm._chart.removeData();
            }
         }
         // Reset the data.
         vm.Data(null); 
      },

      // Create the chart with ChartJS.
      createChart: function (iData, iElement) {
         var labels = [];
         for (var i = 0 ; i < iData.length; i++)
            labels.push(i);

         var chartData = {
            labels: labels,
            datasets: [{
               label: "My live dataset",
               data: iData,
               fillColor: "rgba(217,237,245,0.2)", 
               strokeColor: "#9acfea", 
               pointColor: "#9acfea", 
               pointStrokeColor: "#fff" 
            }]
         };

         return new Chart(iElement.getContext('2d')).Line(chartData, { 
            responsive: true, animation: false });
      }
   }
})();

这是以模块模式风格编写的,具有执行匿名函数——有一篇很好的博客文章讨论了这个问题。但不要认为这是你普通的、司空见惯的 JavaScript 代码;dotNetify 库通过与视图模型名称匹配的命名约定自动找到它,并对其进行预处理,以便“this”引用作用域到客户端视图模型,以及其他事情

此代码提供了您记得绑定到视图的“updateChart”方法。在初始页面加载时,当 Data 属性设置为其初始数据集时,会调用此方法,然后它又调用“createChart”来实例化并填充一个新的 ChartJS 对象。随后,当 Data 属性每 1 秒设置为新值时,该方法会更新 ChartJS 对象。

实时演示

实时演示可在我的网站 http://dotnetify.net/index/livechart 上找到。代码略有不同,因为在这里我也发送了图表的 X 轴标签,但逻辑相同。

此网站托管在满足 SignalR 使用 Web Socket 要求的服务器上,所以我很高兴看到它的实际应用——非常高效的传输,开销很低。顺便说一句,这个演示在 iPad 上看起来也非常好!

后记

第一部分到此结束。编写这个 dotNetify 库对我来说很有趣,因为我知道我真的可以将它应用于实际的 Web 应用程序,并提高生产力——而且更快乐(非常重要!)。

这个库还有很多功能——基本的 CRUD、懒加载、嵌套视图、视图之间的通信等等。请访问网站 http://dotnetify.net 及其 GitHub 存储库。

相关文章

© . All rights reserved.