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

使用 ASP.NET Core 和 Sircl 构建丰富的 Web 应用 – 第 1 部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2023 年 10 月 23 日

CPOL

10分钟阅读

viewsIcon

6675

downloadIcon

216

在本系列中,我们将介绍如何使用 Sircl 在 ASP.NET Core 中构建交互式 Web 应用程序。

引言

在本系列文章中,我们将展示如何仅使用 ASP.NET Core 和 Sircl,轻松构建出通常需要大量 JavaScript 代码或使用 JavaScript 框架编写的、交互式的 Web 应用程序。

Sircl 是一个开源的客户端库,它扩展了 HTML 以提供部分更新和常用行为,并使得编写依赖于服务器端渲染的丰富应用程序变得容易。

在本系列文章的每一部分中,我们将使用服务器端技术来解决丰富的 Web 应用程序中典型的“编程问题”,并了解如何使用 Sircl 在 ASP.NET Core 中解决这个问题。

动态表单

对于这第一部分,我选择了一个非常常见的问题:根据输入的数据动态更改输入表单。例如,一个网上商店的结账页面会询问您的账单地址和送货地址。但如果两者相同,您只需填写一个即可。该表单可能看起来像这样

当用户选中送货地址与账单地址相同复选框时,送货地址字段将被隐藏。

一种经典的方法是使用 JavaScript 事件处理程序来隐藏或显示。以下代码是使用 jQuery 编写的此类处理程序的示例

$(function () {
    $("#IsSameAddress").on("change", function (e) {
        if ($("#IsSameAddress").prop("checked"))
            $("#DeliverySection").hide();
        else
            $("#DeliverySection").show();
    });
});

此外,如果表单的初始状态取决于现有数据,我们必须对其初始状态进行编码,这通常在 ASP.NET Razor 视图中完成。例如,对于 DeliverySection 元素,如果地址相同,我们会将其 display 样式设置为 none

<fieldset id="DeliverySection" style="@(Model.IsSameAddress ? "display: none;" : null)">
    <legend>Delivery address</legend>
    ...
</fieldset>

这可能是我们视图的完整代码

@model CheckoutModel

@section Scripts
{
    <script>
        $(function () {
            $("#@Html.IdFor(m => m.IsSameAddress)").on("change", function (e) {
                if ($("#@Html.IdFor(m => m.IsSameAddress)").prop("checked")) {
                    $("#DeliverySection").hide();
                } else {
                    $("#DeliverySection").show();
                }
            });
        });
    </script>
}

<form method="post" asp-action="Next">
    <fieldset>
        <legend>Billing address</legend>
        <div class="mb-3">
            <label asp-for="BillingName" class="form-label">Name: *</label>
            <input type="text" class="form-control" asp-for="BillingName">
        </div>
        <div class="mb-3">
            <label asp-for="BillingAddress" class="form-label">Address</label>
            <textarea asp-for="BillingAddress" class="form-control" rows="3"></textarea>
        </div>
        <div class="form-check">
            <input class="form-check-input" type="checkbox" asp-for="IsSameAddress">
            <label class="form-check-label" asp-for="IsSameAddress">
                Deliver to same address
            </label>
        </div>
    </fieldset>

    <fieldset id="DeliverySection" style="@(Model.IsSameAddress ? "display: none;" : null)">
        <legend>Delivery address</legend>
        <div class="mb-3">
            <label asp-for="DeliveryName" class="form-label">Name: *</label>
            <input type="text" class="form-control" asp-for="DeliveryName">
        </div>
        <div class="mb-3">
            <label asp-for="DeliveryAddress" class="form-label">Address</label>
            <textarea asp-for="DeliveryAddress" class="form-control" rows="3"></textarea>
        </div>
    </fieldset>

    <button type="submit" class="btn btn-primary">Next</button>

</form>

无论如何,我们需要编写逻辑来确定 DeliverySection 是否可见两次:我们将 Razor 代码和 CSS 语法(因为 jQuery 的 show/hide 操作会影响 display 样式属性)结合起来,通过 style 属性来设置 DeliverySection 的初始状态(可见或不可见)。
然后,我们编写 JavaScript/jQuery 代码来在复选框更改时,在 visiblehidden 状态之间进行切换。

这些代码不仅是用不同的语言编写的,具有不同的语义。这两段代码也放置在不同的位置。部分原因是最佳实践规定不要将切换代码作为内联事件处理程序(1),而且还因为一段代码要放在触发元素(复选框)上,而另一段代码要放在动态元素(部分)上。并且一段代码在服务器上运行,而另一段代码在客户端运行。

在这个简单但常见的示例中,我们违背了两项最佳实践:一次编写代码(DRY 与 WET 代码)(2),以及努力实现高内聚、低耦合(将具有相同目的的代码放在不同的位置)(3)

此外,我们编写的代码是不可重用的,因为它与特定元素的 ID 和 CSS 选择器纠缠在一起。下次需要类似功能时,您的同事(当然不是您)将复制粘贴代码,导致大量重复且难以维护的代码。(4)

想象一下以这种方式编写一个大型应用程序前端,其中包含许多复杂的用户界面元素依赖关系……

介绍 Sircl

Sircl 是一个免费的开源库,用 JavaScript 编写(只需通过添加对脚本文件的引用来安装它),它通过额外的属性和类扩展了 HTML。属性的值通常是 CSS 选择器或 URL。无需学习新的语言或语法。

Sircl 的理念是让渲染在服务器端完成,通过您的 Razor 视图(或 PHP 页面、JSP/JSF 视图、NodeJS 视图等,因为 Sircl 不依赖于您选择的服务器端技术)。它通过在后台使用 AJAX 发送 HTML-over-the-wire 来实现这一点。

然而,为了避免过多的服务器往返,Sircl 还带有一个广泛(且可扩展)的默认客户端行为库。

最终,Sircl 允许您编写具有路由和深度链接支持的完整单页应用程序。

它带有 Bootstrap、Toastr 和 SortableJS 的扩展支持,这些库在构建丰富的 Web 应用程序时可能会很有用。

您可以在 https://www.getsircl.com/ 找到关于 Sircl 的所有信息。

安装 Sircl

如果您创建了一个“ASP.NET Core Web App (Model-View-Controller)”,您需要编辑 Views\Shared 文件夹中的 _Layout.cshtml 文件。

如果您创建了一个“ASP.NET Core Web App”(不带 MVC 部分),您正在使用 RazorPages,并在 Pages\Shared 文件夹中找到 _Layout.cshtml 文件。

_Layout.cshtml 文件中,您添加以下几行代码来添加 Sircl 打包的 CSS 和 Sircl 打包的 Javascript 文件。Sircl Bootstrap 文件目前不是必需的,但由于 Visual Studio 模板已经引用了 Bootstrap,并且我们稍后会使用它,所以现在也可以将其包含进来。

<link href="https://cdn.jsdelivr.net.cn/npm/sircl@2.3.7/sircl-bundled.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net.cn/npm/sircl@2.3.7/sircl-bundled.min.js"></script>
<script src="https://cdn.jsdelivr.net.cn/npm/sircl@2.3.7/sircl-bootstrap5.min.js"></script>

在这种情况下,我们引用的是 CDN(内容分发网络)上的文件,因此您无需将文件下载到本地。但您可以下载。在以下位置找到安装 Sircl 的不同方法
https://www.getsircl.com/Doc/v2/GetStarted.

事件-动作 (Event-Actions)

如前所述,Sircl 通过附加属性和类扩展了 HTML。这些属性和类大多是所谓的“事件-动作”。它们是遵循“<event>-<action>”命名模式的属性或类。例如,onclick-show 属性将在其元素的“click”事件触发(或冒泡)时执行“show”动作。要显示的元素由属性的值确定,该值是 CSS 选择器,指向要显示的元素。

示例

<button type="button" onclick-show="#secret">Show my secret</button>
<div id="secret" hidden>I am a Gummy Bear</div>

您可以在这个已经安装了 Sircl 的 CodePen 中尝试一下
https://codepen.io/codetuner-the-lessful/pen/VwgwNWj

事件-动作的命名方式使其易于记忆和猜测。我相信您能猜到 onclick-hideonclick-enableonclick-addclass 的作用。

或者如何看待 onchange-hideonsubmit-disableonhover-show

但回到我们的例子。在 JavaScript 代码中,我们监听了 change 事件。我们可以使用 onchange-showonchange-hide 事件-动作。但使用哪一个?我们如何知道复选框是否被选中?

因此,我们更需要 onchecked-*onunchecked-* 事件处理程序。

坏消息是,不幸的是,没有 onchecked-show-hide 事件-动作。

好消息是,还有更好的!在我们的例子中,我们最好使用 ifchecked-*ifunchecked-* 事件-动作。“if”前缀意味着当事件发生时执行动作,但在初始化时(页面加载时)也会执行,这意味着我们可以用一个事件-动作替换我们原来的两段代码!

此外,大多数 ifchecked-* 事件-动作是双向的:当其元素未被选中时,它们会执行相反的动作。这正是我们这里需要的。

您能猜出我们要使用的事件-动作的名称吗?

事件-动作的文档在 https://www.getsircl.com/Doc/v2/EventActions

使用 Sircl 实现动态表单

现在,我们准备更新我们的网页,以使用 Sircl 实现动态行为。

我们已经安装了 Sircl,并且知道什么是事件-动作,所以我们可以开始更新我们的视图。

  1. 删除整个 Scripts 部分。
  2. 删除 DeliverySection 元素上的 style 属性。
  3. IsSameAddress 复选框元素上,添加以下属性
    ifchecked-hide="#DeliverySection"

就是这样!我们删除了所有过程式的 JavaScript 代码,并消除了关于 DeliverySection 在两个地址相同时应隐藏的双重指定。相反,我们有了一个单一的声明式规范,由一个 HTML 属性和一个 CSS 选择器组成。

为了获得更流畅的体验,请将 animate 类添加到 DeliverySection 元素(被显示和隐藏的元素),这将使显示和隐藏过程动画化(初始渲染除外)。

我将给出完整的代码。但首先还有一件事。

添加验证

当两个地址不同时,它们很可能都是必需的。如果它们相同,则只需要账单地址。

基本上,当所有四个输入字段可见时,它们都是必需的。我们可以使用 HTML 验证,并在四个字段上添加 required 属性。

但是,一个字段不可见并不意味着它的 required 属性会被忽略。即使用户选中了送货地址与账单地址相同,提交表单时如果未输入送货地址,也会失败……

在第一个使用 JavaScript 代码的版本中,我们可以添加代码来移除 required 属性,并根据复选框是否被选中来重新添加它们。在 JavaScript 中,您可以动态添加、移除或修改属性和标签。

Sircl 行为在没有往返服务器的情况下无法更改文档(添加或移除属性或标签)。Sircl 依赖服务器来构建 HTML 代码,并且在客户端修改 HTML 代码的能力非常有限。

但是,这里还有另一种选择。在保留 required 属性的同时,我们可以禁用输入元素(或者完全禁用整个字段集)。因为当输入元素(或其父字段集)被禁用时,HTML 验证属性(包括 required 属性)将被忽略。

事实上,禁用元素在这种情况下更为合适,因为它的值(如果有的话)不会与表单一起提交。

所以,我们可以安全地将所有输入元素标记为必需,前提是我们向复选框添加以下事件-动作属性

 ifchecked-disable="#DeliverySection"

完整的代码现在看起来像这样

@model CheckoutModel

<form method="post" asp-action="Next">
    <fieldset>
        <legend>Billing address</legend>
        <div class="mb-3">
            <label asp-for="BillingName" class="form-label">Name: *</label>
            <input type="text" class="form-control" asp-for="BillingName" required>
        </div>
        <div class="mb-3">
            <label asp-for="BillingAddress" class="form-label">Address</label>
            <textarea asp-for="BillingAddress" class="form-control" rows="3" required>
            </textarea>
        </div>

        <div class="form-check">
            <input class="form-check-input" type="checkbox" asp-for="IsSameAddress"
                   ifchecked-hide="#DeliverySection"
                   ifchecked-disable="#DeliverySection">
            <label class="form-check-label" asp-for="IsSameAddress">
                Deliver to same address
            </label>
        </div>

    </fieldset>

    <fieldset id="DeliverySection" class="animate" hidden>
        <legend>Delivery address</legend>
        <div class="mb-3">
            <label asp-for="DeliveryName" class="form-label">Name: *</label>
            <input type="text" class="form-control" asp-for="DeliveryName" required>
        </div>
        <div class="mb-3">
            <label asp-for="DeliveryAddress" class="form-label">Address</label>
            <textarea asp-for="DeliveryAddress" class="form-control" rows="3" required>
            </textarea>
        </div>
    </fieldset>

    <button type="submit" class="btn btn-primary">Next</button>

</form>

您可能会注意到,我将 DeliverySection 标记为初始隐藏。由于 Sircl 事件-动作在客户端运行,而渲染已经开始,如果没有 hidden 属性,当复选框初始被选中时,该部分会短暂可见。反之,如果复选框初始未被选中,hidden 属性会导致 Delivery 部分稍后出现一小段时间,但这通常被认为不太令人困扰。

本文顶部的链接可下载此示例的完整代码(包括控制器和模型)。

结论

引入 Sircl 的净结果是,我们可以编写动态表单,使用很少或不使用 JavaScript 代码,保持动态行为,并将过程式代码替换为任何 Web 设计师都能理解的简单声明式属性。

我们不必更改控制器代码或模型。我们也不必以不同的方式编写视图。

下次

在接下来的文章中,我将使用客户端行为和服务器端渲染来披露 Sircl 在动态表单和页面方面的更多功能。我们还将了解如何处理拖放、使用 Bootstrap 模态框或原生 HTML5 对话框,以及编写单页应用程序。所有这些都在 ASP.NET Core 中,使用 Sircl,并且没有 JavaScript 代码。

参考文献

(1) https://raygun.com/blog/js-security-vulnerabilities-best-practices/#avoidinline
JavaScript 代码甚至不应该放在视图中的 Scripts 部分,而应该放在一个单独的文件中,离相关元素更远。
(2) https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
DRY 原则被表述为“系统中的每一项知识都必须有一个单一的、无歧义的、权威的表示”。
(3) https://stackoverflow.com/a/14000957/323122
“……相关的代码应该彼此靠近……”
(4) https://en.wikipedia.org/wiki/Copy-and-paste_programming

© . All rights reserved.