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

Wisej.NET vs Blazor

starIconstarIconstarIconstarIconstarIcon

5.00/5 (18投票s)

2022年8月15日

CPOL

26分钟阅读

viewsIcon

22902

在本文中,我们将了解 Wisej.NET,并探讨它如何解决构建业务线、企业 Web 应用程序的特定挑战。

Blazor 是一个现代的、基于组件的框架,用于使用 .NET 构建网站和应用程序,对于许多项目来说,它是一个很好的选择。

但它不是唯一的选择。

除了 Blazor(包括 Microsoft 的其他选项,如 MVC 和 Razor Pages),我们还有替代方案,其中之一就是 Wisej.NET。

Wisej.NET 是一个 Web 框架,旨在让构建业务 Web 应用程序更快、更容易。如果您是 Wisej.NET 的新手,可以在此处下载社区版本。

它承诺学习曲线平坦、所见即所得设计器、与 Visual Studio 完全集成,以及引入外部组件(来自 SyncFusion 和 Telerik 等供应商)的选项,这样您就可以专注于构建应用程序并满足客户的需求。

乍一看,您可能会认为 Wisej.NET 是 Blazor 的直接替代品,解决类似的问题并使您能够使用 .NET 构建 Web 应用程序。

但实际上,Wisej.NET 解决了构建大型企业 Web 应用程序的特殊挑战,并且其方式值得仔细研究。

所以,请加入我,一起了解 Wisej.NET,并探讨它如何解决构建业务线、企业 Web 应用程序的特定挑战。

我们在寻找什么?

在开始之前,有必要定义一下在这种情况下企业 Web 应用程序是什么以及它们需要能够做什么。

对于此比较,我假设 LOB 企业应用程序管理大量数据,其中数据需要输入、审查和可视化。

如果您正在考虑用于数据输入的表单和以各种格式显示大量数据的屏幕,那么您可能走对了路!

我们将在此比较中探讨的关键因素是

  • 这两个框架如何扩展(特别是它们如何处理数千甚至数百万行数据)
  • 框架如何处理连接丢失和浏览器刷新(状态是否保留?)
  • 处理标准 LOB 应用程序需求所涉及的步骤,例如模式对话框更新页面上的字段

如果您使用过 Blazor,您会看到标准的“新项目”样板 UI(带有计数器和获取数据示例的那个)

让我们首先尝试使用 Wisej.NET 重现这些示例,从天气数据示例开始。

在 Wisej.NET 中从头开始构建 WeatherData 演示

在运行安装过程将 Wisej.NET 添加到 Visual Studio 后,第一步是创建一个新项目。

这里有几个选项,但我选择坚持使用Wisej.NET 3 Web 页面应用程序,因为这似乎最接近标准的 Blazor Web 应用程序(具有页面概念和向这些页面添加组件的能力)。

项目初始化后,您会看到一个相当空白的项目,其中包含一个名为 Page1.cs 的文件。

打开它会弹出 Wisej.NET WYSIWYG 设计器。如果您曾经从事过 WebForms 或 XAML 应用程序的工作,那么这方面的某些方面会让您感到非常熟悉。

为了构建 UI,我们可以从工具箱中拖放控件到页面上。

对于此演示,我将引入一个导航栏和 Flex 布局面板。

导航栏将位于左侧,我将把 Flex 布局面板放在右侧。

我还向导航栏添加了一个项目(它将链接到获取数据/天气预报组件)。

请注意 WeatherForecastPanel 标签值。我们稍后会用到它。

从这里开始,当点击导航栏中的某个项目时,我们需要一种方法来显示不同的“组件”(或面板)。

我将通过将事件处理程序连接到导航栏的 SelectedItemChanged 事件来实现这一点,该事件从选定项目中获取标签名称并使用它来定位(并激活)相关的“面板”。

这是设计器的相关事件。

现在我们需要一些代码来找到相关的“面板”并激活它(这样它就会显示在我之前添加的 Flex 布局面板中)。

private void navigationBar_SelectedItemChanged(object sender, System.EventArgs e)
{
    var panelName = navigationBar1.SelectedItem.Tag;

    this.flexLayoutPanel1.Controls.Clear();
    var panel = (Control)Activator.CreateInstance(Type.GetType($"Wisej.NETWebPageApplication1.Panels.{panelName}"));
    panel.Dock = DockStyle.Fill;
    panel.Parent = this.flexLayoutPanel1;

}

尽管我们才刚刚开始,但这个简单的演示已经开始突出 Blazor 和 Wisej.NET 之间的一些关键区别。

相比之下,标准的 Blazor 新项目模板包含一个 HTML div,其中包含用于导航到应用程序中各个“页面”的 HTML 锚元素。

Blazor 作为 SPA(单页应用程序)运行,因此它会拦截这些点击(当您尝试在页面之间导航时),找到您尝试访问的页面的组件,然后加载该组件并在浏览器中渲染它。

我们正在用 Wisej.NET 做类似的事情,但存在一个关键的哲学差异。

Wisej.NET 旨在构建 Web 应用程序(而非网站)。重点在于维护用户周围的一致状态和上下文,包括他们正在做什么、他们输入了什么数据以及他们在重复的业务流程中达到了哪个阶段。

因此,重点不是在网站页面之间导航,而是在正确的时间向用户显示合适的屏幕,同时跟踪他们如何到达那里的上下文。

这在 Web 应用程序的上下文中是合理的。您可能不希望用户仅仅因为他们碰巧拥有某个 URL 而直接跳转到 UI 的某个部分。在 LOB 应用程序中,您可能需要他们遵循正确的流程才能到达该屏幕!

回到我们的演示,我们需要创建我们之前引用的 WeatherForecastPanel(请参阅前面关于记住标签名称的注意事项)。

为此,我们可以添加一个用户控件(通过 Visual Studio 中的标准“添加 > 新建项目”命令)。

此时我们可以跳开并创建更多“面板”,但让我们先尝试实现这个天气数据演示。

我将从向设计器添加一个 DataGridView 开始。

然后,我可以使用 Blazor 演示中的 WeatherForecastServiceWeatherForecast 类作为灵感,并为 Wisej.NET 实现略作修改的版本。

Wisej.NET\WeatherForecastService

using System;
using System.ComponentModel;
using System.Threading.Tasks;

public class WeatherForecastService
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public Task<BindingList<WeatherForecast>> GetForecastAsync(DateTime startDate, int count = 5)
    {
        var rand = new Random();
        var list = new BindingList<WeatherForecast>();
        for (var i = 0; i < count; i++)
        {
            list.Add(new WeatherForecast
                     {
                         Date = startDate.AddDays(i),
                         TemperatureC = rand.Next(-20, 55),
                         Summary = Summaries[rand.Next(Summaries.Length)]
                     });
        }

        return Task.FromResult(list);
    }
}

此代码生成随机天气数据(以便我们可以在 UI 中显示一些内容)。

请注意,GetForecastAsync 方法已从原始 Blazor 版本修改为返回 BindingList 实例,该实例来自 System.ComponentModel 命名空间,提供了一些“开箱即用”的功能,用于通知 UI 控件数据更改(因此我们可以轻松绑定数据网格等控件)。

WeatherForecast 本身是一个普通的 C# 类

public class WeatherForecast
{
    [DisplayName("Date")]
    public DateTime Date { get; set; }

    [DisplayName("Temp. (C)")]
    public int TemperatureC { get; set; }

    [DisplayName("Temp. (F)")]
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    [DisplayName("Summary")]
    public string Summary { get; set; }

    [DisplayName("Temp. (K)")]
    public double TemperatureK => TemperatureC + 273.15;
}

有了这些类,我们可以将它们设置为驱动我们的数据网格组件。

Wisej.NET\WeatherForecastPanel.cs

public class WeatherForecast
{
    [DisplayName("Date")]
    public DateTime Date { get; set; }

    [DisplayName("Temp. (C)")]
    public int TemperatureC { get; set; }

    [DisplayName("Temp. (F)")]
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    [DisplayName("Summary")]
    public string Summary { get; set; }

    [DisplayName("Temp. (K)")]
    public double TemperatureK => TemperatureC + 273.15;
}

WeatherForecastPanel_Load 已连接到此用户控件的 Load 事件(因此当 WeatherForecastPanel 激活时,它将尝试加载此数据)。

剩下要做的就是调用 WeatherForecastService.GetForecastAsync 来加载 25 条记录(目前)并将它们绑定到数据网格的数据源属性。

此时,两个框架(Wisej.NET 与 Blazor)之间开始出现一些差异。

首先,我们发现 Wisej.NET 创建这种 UI(显示数据的网格)所需的手动工作更少。

创建等效的 Blazor UI 需要我们

  • 手工编写 HTML 和 CSS 来渲染我们的天气数据和导航栏
  • 使用第三方供应商的组件

选择框架自然涉及比较权衡(以及哪种情况对您的特定情况有意义)。这里的权衡似乎是

  • 使用 Wisej.NET,您放弃了对浏览器中渲染内容的直接控制(因为您不是手工编写 HTML,最终会得到 Wisej.NET 为您创建的任何 HTML)
  • 您获得了开发速度(将数据绑定到数据网格比从头编写 HTML 和 CSS 更快)
  • 您“开箱即用”地获得了基本网格功能(如排序、选择行)(您必须自己使用 Blazor 实现)

扩大规模

现在,如果我们需要超过 25 条记录,比如 1000 条甚至 1,000,000 条呢?

为了测试这一点,我们可以添加一些 UI 来选择要加载的记录数量。为此,我将在 Wisej.NET 示例中添加一个组合框,其中包含一些硬编码的值可供选择,以及两个按钮:一个用于加载天气数据,一个用于清除它。

在代码中,我可以读取选定的值,并在单击“加载天气”按钮时使用它来加载请求的记录数量。

Wisej.NET\WeatherForecastPanel.cs

private async void LoadData()
{
    var count = 25;
    int.TryParse(comboBox1.Text, NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out count);

    this.dataGridView1.DataSource = await new WeatherForecastService().GetForecastAsync(DateTime.Now, count);

    this.dataGridView1.Columns["Date"].DefaultCellStyle.Format = "d";
    this.dataGridView1.Columns["TemperatureK"].DefaultCellStyle.Format = "N3";
}

private void button1_Click(object sender, EventArgs e)
{
    LoadData();
}

现在我们运行这个程序,Wisej.NET 可以轻松处理大量记录。

在 1,000 条和 100,000 条记录之间切换非常即时,加载 1,000,000 条记录也很快。

Loading multiple records

Wisej.NET DataGrid 提供排序功能(通过单击列名切换)和选择特定行的功能。

您还可以切换特定列的可见性

切换到 Blazor,我们可以使用 @foreach 循环遍历数据,为每条记录渲染表格行。

Blazor\FetchData.razor

@foreach (var forecast in forecasts)
{
    <tr>
        <td>@forecast.Date.ToShortDateString()</td>
        <td>@forecast.TemperatureC</td>
        <td>@forecast.TemperatureF</td>
        <td>@forecast.Summary</td>
    </tr>
}

然后,我们可以使用 HTML select 元素实现 UI 来选择记录数量。

Blazor\FetchData.razor

<!-- existing markup -->

<select @onchange="CountChanged">
    <option>10</option>
    <option>10,000</option>
    <option>100,000</option>
    <option>1,000,000</option>
</select>
@code {

    protected async Task CountChanged(ChangeEventArgs e)
    {
        int.TryParse(e.Value.ToString(), 
                     System.Globalization.NumberStyles.AllowThousands, 
                     CultureInfo.InvariantCulture, out var count);

        await LoadData(count);
    }

    protected async Task LoadData(int count = 0)
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now, count);
    }

}

现在,如果我们尝试加载 1,000,000 条记录并使用此 foreach 循环显示它们,我们会遇到麻烦。

在测试时,我发现浏览器最终会放弃并询问您是否要继续等待页面响应。

Blazor 解决此问题的方法是改用专用的 Virtualize 组件。

使用 Virtualize,我们确保只渲染可见的记录,这使得页面在我们将更多记录投入表格时保持响应。

我们可能还需要修正表格的高度(这样我们就可以在表格中滚动记录而无需滚动整个页面)。

这是我们用于实现虚拟化并修正表格高度的标记和 CSS。

Blazor\FetchData.razor

<div class="weatherPanel">
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            <Virtualize Items="@forecasts" Context="forecast">
                 <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            </Virtualize>
        </tbody>
    </table>
</div>
.table {
    height: 100%;
}

.weatherPanel {
    height: 32em;
    display: block;
    overflow: scroll;
}

这是实际运行效果

Blazor Weather Data Example

这里要注意的一点是,当我们滚动记录时,Blazor 在现有 HTML 元素中重新渲染新数据时会有一个明显的“延迟”。

从这里开始,为了更好地匹配 Wisej.NET 版本,我们需要实现排序、通过点击选择行的功能以及固定表格标题(这样它们在向下滚动时不会消失),并实现显示和隐藏列的功能。

实际上,Wisej.NET 版本已经准备就绪(得益于 DataGrid 的内置功能)。

我们需要花费更多时间在 Blazor 等效版本上,使其保持一致(如果我们想要排序和突出显示选定记录等附加功能)。

刷新时会发生什么?

在任何 Web 场景中,总是存在连接丢失或用户意外刷新(或离开)当前页面的风险。

这可能是一个重大问题,尤其是当您的用户正在进行多步骤流程(想想多阶段向导或多“页”表单)时,如果他们因为连接丢失或无意中离开了应用程序而最终丢失了进度。

这是两个框架差异显著的一个领域。

从我们的 Wisej.NET 天气数据页面开始,如果我选择显示 100 万条记录的选项,让它们出现在数据网格中,然后点击刷新,页面会完全按照之前的样子重新加载。

我们仍然在同一个屏幕上,组合框中选择了 1,000,000,数据网格中加载了相同的 100 万条天气记录。

刷新页面前后(Wisej.NET)

事实上,如果我们对网格进行了排序或切换了特定列的可见性,所有这些“状态”在刷新时都会保留。

虽然 Blazor Server 确实尝试保留一些应用程序状态(稍后会详细介绍),但实际上,如果我们在 Blazor 等效项中点击刷新,我们仍然停留在同一页面(使用相同的组件),但我们的选择会丢失,并且表格会变得空白。

刷新前

刷新后

在这方面,Wisej.NET 在 LOB 应用程序方面具有优势,它会自动为您的用户提供有状态的“会话”,而无需您自己或通过第三方库实现状态跟踪(持久化和恢复该状态的能力)。

这在您希望提供“桌面般”体验的应用程序中特别有用,您的用户可以打开多个“窗口”或表单,这些窗口或表单可以移动、最大化、最小化、关闭等。

Wisej.NET 实现了这一点,同时还记住了用户在应用程序中导航时 UI 的状态。

这里还有一个例子:我在设计时向屏幕添加了几个面板,并将它们标记为可移动(因此用户可以在应用程序本身中移动它们)。

初始加载后

当应用程序在浏览器中运行时,可以拖动这些窗口。

移动面板后,当页面刷新时,面板会保持其(新)位置。

刷新前后(我在浏览器中移动面板后)

当我们考虑常见的 LOB 任务(如填写表单)时,这会变得更有趣,如果刷新应用程序或离开表单,Wisej.NET 会保留部分完成的表单状态。

Wisej.NET 如何处理会话

Wisej.NET 能够在刷新时恢复 UI 的原因在于它处理会话的方式。

不要与身份验证或用户会话混淆,Wisej.NET 会话旨在通过 Web 提供类似桌面应用程序的体验。

可以把它想象成运行一个桌面应用程序的实例。从您启动应用程序的那一刻起,到您关闭它(或重新启动机器)的那一刻,该应用程序的运行实例可以跟踪您的操作、哪些窗口是可见的(或隐藏的)、您当前正在关注的 UI 的哪些部分。

Wisej.NET 会话采用类似的方法,但通过 Web 运行。当您访问应用程序时,会启动一个新的会话。此时,您可以离开,回来,刷新,该会话仍保持活动状态。

会话跟踪应用程序的当前状态,包括 UI 模型(哪些窗口可见/打开、用户输入的数据、哪些模型当前正在等待响应等)

Blazor 如何处理会话

要了解 Blazor 如何处理“会话”状态,我们需要区分 Blazor 的两种托管模型。

Blazor WASM 完全在浏览器中运行,类似于 JavaScript 单页应用程序(例如 React 或 Angular)。使用 Blazor WASM,没有内置的会话状态持久性。

例如,假设用户在 Blazor WASM 应用程序中完成多页表单时中途离开。如果他们的连接中断(他们意外地离开了页面或完成了当天的工作并希望明天返回表单),Blazor WASM 不会尝试持久化与他们的进度相关的任何状态。

Blazor Server 略有不同。它使用电路来尝试跟踪应用程序的当前状态(对于每个用户/连接)。

组件状态(哪些组件可见,当前显示哪些数据)存储在服务器上。当您与 Blazor 服务器应用程序交互时,这些交互会发送到服务器。然后,Blazor 通过套接字连接向浏览器发送消息,指示它响应不断变化的组件状态来更新 UI (DOM)。

通常,Blazor Server 将尝试让用户连接到同一个电路。

其理念是,如果连接暂时中断,用户将重新连接到同一电路(这样他们就不会丢失迄今为止输入的任何信息)。

实际上,这并非 100% 可靠;Blazor 可能会因多种原因将您连接到不同的电路,包括

  • 服务器因内存压力而终止了原始电路
  • 您的应用程序通过负载平衡配置中的多个服务器提供服务。如果单个服务器发生故障(或在不再需要时被删除),则当用户尝试重新连接时,用于用户连接的原始服务器可能变得不可用

如果刷新,您不仅会连接到新电路,还会丢失浏览器内存中保存的任何状态(例如,如果您通过 JavaScript 互操作设置了值)。

从技术上讲,可以在跨电路中保持状态,但您需要开发自己的解决方案,将应用程序状态保存在服务器内存之外的某个位置(例如数据库)。

值得注意的是,在这种情况下您无法持久化 UI 状态。

例如,虽然您可以编写代码来存储用户输入到表单中的值,但如果他们只完成了一半表单并且弹出了一个模式,那么在他们切换到另一个电路的情况下,您将无法将他们放回到那个表单上,并且模式仍然打开。

相反,Wisej.NET 会跟踪该状态,并让您回到相同的“屏幕”上,并打开相同的模式。

Wisej.NET 保持 UI 应用程序模型同步

现在似乎是时候探讨 Wisej.NET 管理客户端和服务器之间连接的方法了。

当您启动应用程序并通过浏览器访问它时,Wisej.NET 会维护您的应用程序 UI 模型的两种表示:一种在客户端(浏览器),一种在服务器上。

从用户的角度来看,这是无缝的,但为开发开启了一些有趣的可能性,因为您可以编写 JavaScript 代码来与应用程序在浏览器中的控件进行交互,Wisej.NET 会同时保持相关的服务器端对应项同步。

结果是您可以使用 JavaScript 遍历应用程序的 UI 模型(从顶级 App 开始)并直接与元素进行交互。

让我们以前面的多面板示例为例。

我们可以遍历到第一个面板(富有想象力的命名为“Panel1”)并调用 hide 方法使控件消失。

当我们调用此方法(通过 JavaScript)时,Wisej.NET 将在浏览器中隐藏面板,并确保面板的服务器端实例相应地更新。

为了测试这一点,我将事件处理程序连接到面板的 Disappear 事件(在 C# 代码中)

MultiWindow.cs

private void panel1_Disappear(object sender, EventArgs e)
{
    // this method was invoked
}

然后运行以下命令(通过 JavaScript 控制台)

App.Page1.flexLayoutPanel1.MultiWindow.panel1.hide()

面板消失,并且在服务器上调用了相应的 panel1_Disappear 事件

这是一个有用的功能,尤其因为它意味着您可以编写 JavaScript 代码,而无需担心服务器端模型不同步,和/或应用不一致的业务规则(在浏览器中触发的任何交互都将在服务器上正确处理)。

Blazor 中没有直接的等效项。最接近的是通过 JS 互操作与 JavaScript 交互的能力。这需要手动“管道”和实现特定方法(在 Blazor 组件中)才能通过 JavaScript 调用它们。

退一步看,不起眼的计数器

在解决另一个常见的业务应用程序需求(如何显示模态对话框)之前,让我们退一步,考虑一下不起眼的计数器演示。

计数器可能是您启动 Blazor 项目时看到的第一个演示

当您点击按钮时,计数器会递增。

为了使用 Wisej.NET 重建它,我们可以创建一个简单的页面(或表单、用户控件等),其中包含一个按钮、一个标签和一些代码,用于在点击按钮时更新计数器的值。

public partial class Counter : FlexLayoutPanel
{
    int value = 0;

    public Counter()
    {
        InitializeComponent();
        this.label1.Text = value.ToString();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        value++;
        this.label1.Text = value.ToString();
    }
}

在 UI 中,我有两个标签(一个显示值,另一个显示值旁边的文本),以及按钮。

这个演示虽然简单,但展示了两个框架之间的关键区别:Blazor 倾向于采用声明式方法,而 Wisej.NET(通常)倾向于命令式方法(取决于具体的用例)。

这是 Blazor 计数器的等效项

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

请注意,这依赖于绑定(例如,我们在标记中显示 @currentCount 的当前值)。如果绑定值更新,UI 元素会相应地做出反应(并显示新值)。

使用 Wisej.NET,我们更倾向于采用命令式方法,即我们发布命令直接更新 UI。

this.label1.Text = value.ToString();

可以将某些 Wisej.NET 控件绑定到数据(这样,如果数据源发生更改,控件就会反映更新的数据)。事实上,我们在天气数据演示中看到了一个例子。但总的来说,您的 Wisej.NET 表单将采用更命令式的方法。

两种方法都有需要考虑的权衡

  • 命令式代码更容易编写,尤其是在建模逻辑上按顺序的业务逻辑时(显示对话框、提示用户输入额外数据、验证提供的数据等)
  • 声明式方法使在检查 UI 时更容易识别哪些字段影响 UI(因为绑定在标记中可见)
  • 声明式方法固有的解耦(状态在一个地方改变,UI 自动响应)可能使追踪重要逻辑所在的位置变得更加困难,尤其是在远离 UI 的代码中进行更改时
  • 您可能更习惯于一种风格而不是另一种风格(在这种情况下,您可能更喜欢使用您已经熟悉的风格来提高工作效率)

模态窗口中有什么?

LOB/企业 Web 应用程序中的一个常见需求是通过模态窗口呈现或请求信息。

对于某些业务流程,在模态窗口得到确认和/或提供所需信息之前,阻止任何进一步的进展非常重要。

Wisej.NET 模态窗口

Wisej.NET 内置支持在等待用户响应时(当显示模态对话框时)暂停服务器上的执行。

例如,一个删除按钮(比如,删除一个客户)。

可以响应按钮点击显示消息框,该消息框显示确认对话框,并阻止任何其他代码执行,直到用户做出选择。

private void btnDelete_Click(object sender, EventArgs e)
{
    if (MessageBox.Show("Are you sure?", buttons: MessageBoxButtons.YesNo) == DialogResult.Yes)
    {
        this.btnDelete.Enabled = false;
        this.btnDelete.Text = "Deleted";
    }
}

当用户点击删除按钮时,他们必须选择才能继续。

但是,如果我们在继续之前想要请求更多信息呢?

为此,我们可以使用自定义表单并将其显示为模态窗口。

这里,EnterPassword 是一个 form(您可以添加到应用程序中的一种 Wisej.NET 容器)。

Wisej.NET/EnterPassword(表单)

public partial class EnterPassword : Form
{
    public string Password { get; set; }

    public EnterPassword()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        this.Password = txtPassword.Text;
        Close();
    }

    private void button2_Click(object sender, EventArgs e)
    {
        Close();
    }
}

它的作用是向用户请求密码并将其存储在 Password 属性中。

它在代码中不可见,但我也为每个按钮设置了 DialogResult

这确保了当我们以对话框(模态)形式显示此“表单”时,我们将获得对话框结果(然后我们可以使用该结果来驱动相关的业务逻辑)。

从这里,我们可以将此表单显示为对话框(模态),获取输入的密码,并使用它来驱动业务逻辑。

Wisej.NET/ModalPage.cs

private void btnDelete_Click(object sender, EventArgs e)
{
    using (var dialog = new EnterPassword())
    {
        // show the `EnterPassword` form as a modal (dialog)
        var result = dialog.ShowDialog();

        if (result == DialogResult.OK)
        {
            // grab the entered password
            var password = dialog.Password;

            // here we could check the password,
            // and delete the user if password OK                    

            this.btnDelete.Enabled = false;
            this.btnDelete.Text = "Deleted";
        }
    }
}

在这种情况下,我们显示模态窗口,然后通过公共 Password 属性访问输入的密码。

当我们在浏览器中运行此操作时,单击“删除”按钮会以对话框(模态)形式显示 EnterPassword 表单,此时我们需要关闭对话框才能继续(通过输入密码并单击“继续”或单击“取消”按钮)。

刷新友好

因为 Wisej.NET 保留了应用程序的当前 UI 状态(对于任何给定的“会话”),所以模态窗口的状态也会在刷新时恢复。

如果我们点击了一个按钮并弹出了一个模态窗口,服务器执行就会暂停。

然后我们可以刷新页面,模态窗口仍然会打开。

我们可以继续(通过点击其中一个按钮),此时执行将恢复,并且相关的业务逻辑将执行。

Wisej.NET modal state after refresh

这种“状态持久性”意味着我们可以实现复杂的业务流程,包括多个模态窗口(可能根据用户输入显示不同的选项),并且安全地知道这些流程的进度将被保留,即使连接暂时丢失。

Blazor 模态框

Blazor 不附带任何内置的模态功能,因此我们的选择是

  • 自己实现一个模态弹出窗口(通过创建覆盖层覆盖在页面前面,以及一个用作模态窗口的 HTML 元素)
  • 使用第三方库,例如 Blazored Modal

如果我们要使 Blazor 中的 Wisej.NET 功能(使用 Blazored.Modal)匹配,我们需要

  • 添加对 Blazored.Modal Nuget 包的引用
  • 引用 Blazored.Modal JavaScript 和 CSS 文件
  • 注册 IModalService
  • 设置一个名为级联参数的东西,使 IModalService 可用于我们的组件

一旦(一次性)设置完成,我们就可以在 Blazor 组件中显示模态窗口。

这是我们删除确认示例的 Blazored Modal 等效项。

<button @onclick="@(()=>DeleteCustomer(customer.Id))">Delete</button>
@code {

    private Customer customer = new Customer { Id = 1 };    

    [CascadingParameter] public IModalService Modal { get; set; }

    async Task DeleteCustomer(int customerId)
    {
        var modal = Modal.Show<ConfirmPassword>("Enter Password to proceed");
        var result = await modal.Result;

        if (result.Cancelled)
        {
            // Modal was cancelled
        }
        else
        {
            // Modal was closed
            var password = result.Data;
        }
    }
}

我们正在利用 IModalService 级联参数来显示另一个组件 (EnterPassword) 并等待结果。

ConfirmPassword case 是另一个 Blazor 组件。

ConfirmPassword.razor

<label>
    Enter your password to continue
    <input type="password" @bind-value="password" @bind-value:event="oninput"/>
</label>

<button @onclick="Continue">Continue</button>
@code {
    [CascadingParameter] BlazoredModalInstance ModalInstance { get; set; }

    string password;

    private async Task Continue()
    {
        await ModalInstance.CloseAsync(ModalResult.Ok(password));
    }
}

请注意,在此示例中,ConfirmPassword 需要 BlazoredModalInstance 级联参数才能直接关闭模型。

与 Wisej.NET 示例不同。如果模态窗口打开时刷新,任何现有的“进度”都将丢失,您将不得不重新开始。

关于这两种方法的一些观察

  • 与数据网格示例一样,您可以通过 Blazor 更直接地控制模态窗口的外观(因为您是自己编写 HTML 和 CSS)
  • 另一方面,使用 Blazor,您需要自己编写 HTML 和 CSS,这会占用更多的开发时间(并且您在很大程度上依赖您或您团队的 CSS 技能)
  • Blazor 依赖第三方软件包来支持模态窗口(或者您可以自己实现)
  • Wisej.NET 具有内置的模态功能(无需求助于第三方),并允许您根据需要设计模态样式(通过所见即所得编辑器和设置各种控制外观的属性的能力)
  • Wisej.NET 具有内置的状态跟踪功能,可以抵御连接丢失(或“不稳定”)的情况
  • Wisej.NET 的方法使得实现复杂的业务逻辑成为可能,其中(多个)模态窗口是流程的一部分。您可以实现多步骤流程,其中在不同点显示模态窗口,服务器将记住您在该流程中的位置,即使应用程序刷新也会记住

关于最后一点,这里有一个( admittedly contrived)使用 Wisej.NET 在各个阶段显示带有模态对话框的消息框的多阶段流程示例。

private void btnDelete_Click(object sender, EventArgs e)
{
    using (var dialog = new EnterPassword())
    {
        var result = dialog.ShowDialog();

        if (result == DialogResult.OK)
        {
            // grab the entered password
            var password = dialog.Password;

            // here we could check the password,
            // and only continue if it's OK                   

            if (MessageBox.Show("Are you sure?", buttons: MessageBoxButtons.YesNo) == DialogResult.Yes)
            {
                if (MessageBox.Show("Are you really sure?", buttons: MessageBoxButtons.YesNo) == DialogResult.Yes)
                {
                    // delete the customer then…
                    this.btnDelete.Enabled = false;
                    this.btnDelete.Text = "Deleted";
                }
            }
        }
    }
}

显然这里有一些重构的空间,但请注意,您可以中途进行此过程,刷新,然后无缝地从上次离开的地方恢复。

动态控件

最后,在我们对 Wisej.NET 的快速浏览中,让我们看看如何实现动态 UI,即我们可以在运行时向 UI 添加控件。

假设我们想实现某种动态仪表板,其中包含用户可以添加或删除的小部件。

Wisej.NET 的动态控件

使用 Wisej.NET,我们可以以编程方式将任何控件添加到容器中。

例如,这里我将简单的 Counter 演示(见上文)添加到 FlowLayoutPanel 中。

container.Controls.Add(new Counter());

Wisej.NET 的 FlowLayoutPanel(以及 Wisej.NET 中的其他面板)有一个非常方便的工具集合,您可以在其中向面板的工具栏添加图标。

这是一个例子

为了进一步测试,我向这个 FlowLayoutPanel 添加了一个“添加”按钮。

从那里,我们可以编写一些代码来处理工具被点击,然后采取相应的行动。

private void panel1_ToolClick(object sender, ToolClickEventArgs e)
{
    var container = (Panel)sender;

    switch (e.Tool.Name)
    {
        case "Add":
            container.Controls.Add(new Counter());
            break;
        default:
            break;
    }
}

现在,当我们点击该按钮时,新的 Counter 实例会被添加到面板中。

每个实例都是独立的,这意味着我们可以单独递增每个计数器。

与我们目前看到的所有情况一样,此状态在刷新时会保留(因此您可以添加多个计数器,分别递增它们,刷新后它们仍然存在,显示各自的计数)。

我们还可以将数据传递给控件。例如,假设我们想将初始计数传递给我们的计数器。我们可以在这里使用标准的 C# 方法,创建一个接受初始计数的构造函数

Counter.cs

public Counter(int initialCount = 0)
{
    InitializeComponent();

    this.value = initialCount;
    this.label1.Text = value.ToString();
}

然后,当我们向面板添加计数器时,使用该构造函数。

container.Controls.Add(new Counter(100));

Blazor 的动态控件

Blazor(从 .NET 6 开始)有一个专门的 DynamicComponent,您可以使用它来渲染其他组件。

<DynamicComponent Type="Counter"/>

这种声明式方法意味着我们还可以通过遍历列表来渲染多个动态组件。

@code {
    readonly List<Type> _components = new List<Type>
    {
        typeof(WidgetA),
        typeof(WidgetB)
    };
}
@foreach(var component in _components){
    <DynamicComponent Type="@component"/>
}

在此示例中,我们创建了一个组件类型列表,然后遍历每个组件,使用 DynamicComponent 来渲染相关组件。

要向屏幕添加新组件,我们需要将它们添加到 _components 列表中,此时 Blazor 将重新渲染并显示新添加的组件。

我们可以使用字典(字符串、对象)将数据传递给动态渲染的 Blazor 组件。

private Dictionary<string, object> exampleParameters 
    = new Dictionary<string, object>
    {
        ["InitialCount"] = 100
    };

然后我们可以将它作为 Parameters 转发给动态组件

<DynamicComponent Type="Counter" Parameters="exampleParameters"/>

总的来说,Blazor 方法的抽象程度更高一些(与 Wisej.NET 相比,Wisej.NET 可以更直接地创建控件,包括参数/数据,并将其添加到容器中)。

结束语

这是我第一次探索 Wisej.NET,我的总体感觉是它以一种干净、可扩展的方式解决了 LOB/企业应用程序的一些常见需求。

如果您正在构建一个状态很重要的应用程序(例如控件的当前可见性、部分完成的表单数据、可能持续一段时间的业务流程),Wisej.NET 提供了大量开箱即用的功能。

我们在本文中只触及了表面,但对我来说,Wisej.NET 的显著特点是

  • 内置状态(应用程序和 UI)跟踪
  • 预构建控件(用于常见的 LOB 需求,例如通过网格显示数据)
  • 可扩展架构(可处理数千个连接)
  • 独特的客户端/服务器方法,其中控件在浏览器和服务器上实例化,两者的交互都会在另一端反映
  • 现有 WebForms/WinForms 应用程序的易于访问的迁移路径

还有一些我们没有涉及的其他因素,包括主题管理和使用组件供应商提供的第三方控件的能力,Wisej.NET 都提供了这些功能。

过去我曾从事过一些 WebForms(以及 WinForms)Web 应用程序的工作,Wisej.NET 构建“表单”的方法非常熟悉。如果您目前正在维护一个 WebForms(或 WinForms)应用程序,我可以看到 Wisej.NET 将是一个非常自然的迁移。

转到 Blazor,我认为 Blazor 非常适合您希望(或需要)精确控制 HTML 和 CSS 的网站和应用程序。Blazor 的 UI 模型基于符合标准的 HTML 和 CSS。当然,这也意味着您必须自己编写所有内容或求助于第三方供应商。

Blazor 在很大程度上是无主见的(关于 UI 控件),因此您通常需要投入时间/精力来构建常见任务的 UI,例如显示模态窗口或求助于第三方解决方案。

对于我们在此探讨的一些常见的 LOB/企业应用程序需求尤其如此。实际上,Blazor 是一个通用 Web 应用程序框架,具有许多潜在用例。因此,对于这些常见需求,您可能会在 Blazor 上花费更多时间在管道和创建定制解决方案(或使用第三方库)上。

最终,任何技术/框架的选择都归结为权衡和选择最适合工作的工具。

如果您正在寻找构建(或迁移)LOB 应用程序,我当然可以推荐 Wisej.NET,即使只是为了您自己确定它是否能解决您希望解决的关键挑战。

下载 Wisej.NET

您可以在此处下载 Wisej.NET 的免费社区版本。

© . All rights reserved.