Blazor 是将 C# 引入浏览器的 Microsoft 试验框架,正好可以填补欠缺的 C# 一环。如今,C# 程序员可以编写桌面、服务器端 Web、云、电话、平板电脑、手表、电视和 IoT 应用程序。

Blazor 填补了欠缺的一环,C# 开发人员现在可以直接在用户浏览器中共享代码和业务逻辑。对于 C# 开发人员来说,这是一项十分强大的功能,可显著提升工作效率。

本文将展示常见的代码共享用例。我将展示如何在 Blazor 客户端和 WebAPI 服务器应用程序之间共享验证逻辑。目前,你不仅要在服务器中验证输入,还要在客户端浏览器中验证输入。新式 Web 应用程序的用户希望获得准实时反馈。在填写长窗体并单击“提交”后仅看到红色错误返回的日子已经一去不复返了。

在浏览器中运行的 Blazor Web 应用程序可以与 C# 后端服务器共享代码。可以将逻辑放入共享库中,并在前端和后端使用它。这会带来很多好处。

可以将所有规则都集中放置在一处,并知道只需在一处更新它们。它们的工作方式确实相同,因为它们是相同的代码。

在客户端和服务器逻辑并不总是完全相同的情况下,可以节省大量测试和故障排除时间。

也许最值得一提的是,可以在客户端和服务器上使用一个库进行验证。

以前,JavaScript 前端强制开发人员编写两个版本的验证规则:一个是用适用于前端的 JavaScript 编写,另一个是用适用于后端的语言编写。若要尝试解决这种不匹配问题,需要涉及复杂的规则框架和额外的抽象层。使用 Blazor,可以在客户端和服务器上运行同一.NET Core 库。

虽然 Blazor 仍是试验框架,但它的进展迅速。生成此示例前,请先确保已安装正确版本的 Visual Studio、.NET Core SDK 和 Blazor 语言服务。有关入门步骤,请访问 blazor.net。

新建 Blazor 应用程序

首先,新建 Blazor 应用程序。

在“新建项目”对话框中,依次单击“ASP.NET Core Web 应用程序”和“确定”,再选择图 1 所示对话框中的“Blazor”图标。单击“确定”。

这会创建默认的 Blazor 示例应用程序。如果已试用过 Blazer,便会对此默认应用程序很熟悉。

图 1:选择 Blazor 应用程序

新的注册窗体将展示验证业务规则的共享逻辑。图 2 展示了包含“名字”、“姓氏”、“电子邮件地址”和“电话”字段的简单窗体。

在此示例中,它会验证所有字段是否都为必填、姓名字段是否有长度上限,以及电子邮件地址和电话字段的格式是否正确。它会在每个字段下显示错误消息,这些消息会在用户键入内容的同时更新。最后,只有在没有错误的情况下,“注册”按钮才处于启用状态。

图 2:注册窗体

共享库

所有需要在服务器和 Blazor 客户端之间共享的代码都位于一个独立的共享库项目中。共享库包含模型类和非常简单的验证引擎。模型类保留注册窗体中的数据字段。该命令如下所示:

public class RegistrationData : ModelBase{  [RequiredRule]  [MaxLengthRule(50)]public String FirstName { get; set; }  [RequiredRule]  [MaxLengthRule(50)]public String LastName { get; set; }  [EmailRule]public String Email { get; set; }  [PhoneRule]public String Phone { get; set; }}

RegistrationData 类继承自 ModelBase 类,后者包含所有可用于验证规则并返回绑定到 Blazor 页面的错误消息的逻辑。每个字段都使用映射到验证规则的属性进行修饰。我选择了创建非常简单的模型,它很像实体框架 (EF) 数据注释模型。此模型的所有逻辑都包含在共享库中。

ModelBase 类包含 Blazor 客户端应用程序或服务器应用程序可用来确定是否有任何验证错误的方法。它还会在此模型更改时触发事件,以便客户端能够更新 UI。任何模型类都可以继承自它,并自动获取所有验证引擎逻辑。

首先,我将在 SharedLibrary 项目中新建 ModelBase 类,如下所示:

public class ModelBase{}

错误和规则

现在,我将向 ModelBase 类添加包含验证错误列表的专用字典。_errors 字典先以字段名称为键,再以规则名称为键。值是要显示的实际错误消息。

通过此设置,可以轻松确定特定字段是否有验证错误,并快速检索错误消息。

代码如下:

private Dictionary<String, Dictionary<String, String>> _errors =new Dictionary<string, Dictionary<string, string>>();

现在,我将添加 AddError 方法,以将错误输入内部错误字典。

AddError 有 fieldName、ruleName 和 errorText 参数。它先搜索内部错误字典,并删除已有条目,再添加新的错误条目,如下面的代码所示:

private void AddError(String fieldName, String ruleName, String errorText){if (!_errors.ContainsKey(fieldName)) { _errors.Add(fieldName,new Dictionary<string, string>()); }if (_errors[fieldName].ContainsKey(ruleName))     { _errors[fieldName].Remove(ruleName); }  _errors[fieldName].Add(ruleName, errorText);  OnModelChanged();}

最后,我将添加 RemoveError 方法,它接受 fieldName 和 ruleName 参数,并在内部错误字典中搜索并删除匹配的错误。代码如下:

private void RemoveError(String fieldName, String ruleName){if (!_errors.ContainsKey(fieldName)) { _errors.Add(fieldName,new Dictionary<string, string>()); }if (_errors[fieldName].ContainsKey(ruleName))     { _errors[fieldName].Remove(ruleName);    OnModelChanged();  }}

下一步是添加 CheckRules 函数,这些函数负责查找并执行附加到此模型的验证规则。有两种不同的 CheckRules 函数:一种是缺少参数,但对所有字段验证全部规则;另一种有 fieldName 参数,并仅验证特定字段。在字段更新时,使用的是第二种函数,并立即对此字段验证规则。

CheckRules 函数使用反射来查找附加到字段的属性列表。然后,它测试每个属性,以确定属性类型是否为 IModelRule。找到 IModelRule 后,它调用 Validate 方法,并返回结果,如图 3 所示。

图 3:CheckRules 函数

public void CheckRules(String fieldName){var propertyInfo = this.GetType().GetProperty(fieldName);var attrInfos = propertyInfo.GetCustomAttributes(true);foreach (var attrInfo in attrInfos)  {if (attrInfo is IModelRule modelrule)    {var value = propertyInfo.GetValue(this);var result = modelrule.Validate(fieldName, value);if (result.IsValid)      {        RemoveError(fieldName, attrInfo.GetType().Name);      }else      {        AddError(fieldName, attrInfo.GetType().Name, result.Message);      }    }  }}public bool CheckRules(){foreach (var propInfo in this.GetType().GetProperties(    System.Reflection.BindingFlags.Public |      System.Reflection.BindingFlags.Instance))    CheckRules(propInfo.Name);return HasErrors();}

接下来,我将添加 Errors 函数。此函数需要使用 fieldname 参数,并返回包含相应字段的错误列表的字符串。它使用内部 _errors 字典来确定相应字段是否有任何错误,如下所示:

public String Errors(String fieldName){if (!_errors.ContainsKey(fieldName)) { _errors.Add(fieldName,new Dictionary<string, string>()); }  System.Text.StringBuilder sb = new System.Text.StringBuilder();foreach (var value in _errors[fieldName].Values)    sb.AppendLine(value);return sb.ToString();}

现在,我需要添加 HasErrors 函数,它会在此模型的任意字段有任何错误时返回 true。客户端使用此方法来确定是否应启用“注册”按钮。

另外,WebAPI 服务器也使用此方法来确定传入的模型数据是否有错误。此函数的代码如下:

public bool HasErrors(){foreach (var key in _errors.Keys)if (_errors[key].Keys.Count > 0) { return true; }return false;}

值和事件

是时候添加 GetValue 方法了,它需要使用 fieldname 参数,并使用反射来查找此模型中的字段并返回字段值。Blazor 客户端使用此方法来检索当前值,并在输入框中显示它,如下所示:

public String GetValue(String fieldName){var propertyInfo = this.GetType().GetProperty(fieldName);var value = propertyInfo.GetValue(this);if (value != null) { return value.ToString(); }return String.Empty;}

现在,添加 SetValue 方法。它使用反射来查找此模型中的字段,并更新字段值。然后,它触发 CheckRules 方法,以对相应字段验证所有规则。Blazor 客户端使用此方法,以在用户在输入文本框中键入内容的同时更新值。代码如下:

public void SetValue(String fieldName, object value){var propertyInfo = this.GetType().GetProperty(fieldName);  propertyInfo.SetValue(this, value);  CheckRules(fieldName);}

最后,我添加 ModelChanged 事件。如果此模型中的值已更改或在内部错误字典中添加或删除了验证规则,便会触发这个事件。Blazor 客户端侦听此事件,并在事件触发时更新 UI。正因为此,显示的错误会更新,如下面的代码所示:

public event EventHandler<EventArgs> ModelChanged;protected void OnModelChanged(){  ModelChanged?.Invoke(this, new EventArgs());}

得承认此验证引擎的设计非常简单,还有很多改进机会。在生产业务应用程序中,设置错误的严重性级别(如“信息”、“警告”和“错误”)会很有用。在某些情况下,如果无需修改代码,即可从配置文件动态加载规则,将会很有帮助。我不是在提倡创建你自己的验证引擎;只是有很多选择。此验证引擎既要足够好,以便演示实际示例;又要足够简单,以适应本文且易于理解。

创建规则

此时,有包含窗体字段的 RegistrationData 类。

此类中的字段使用 RequiredRule 和 EmailRule 等属性进行修饰。

RegistrationData 类继承自 ModelBase 类,后者包含所有用于验证规则并向客户端通知更改的逻辑。验证引擎的最后一部分是规则逻辑本身。接下来,我将对此进行探索。

首先,我在 SharedLibrary 中新建 IModelRule 类。

此规则由一个返回 ValidationResult 的 Validate 方法组成。每个规则都必须实现 IModelRule 接口,如下所示:

public interface IModelRule{ValidationResult Validate(String fieldName, object fieldValue);}

接下来,我在 SharedLibrary 中新建 ValidationResult 类,它由两个字段组成。IsValid 字段指明规则是否有效,而 Message 字段则包含要在规则无效时显示的错误消息。代码如下所示:

public class ValidationResult{public bool IsValid { get; set; }public String Message { get; set; }}

示例应用程序使用四个不同的规则,所有规则都是继承自 Attribute 类并实现 IModelRule 接口的公共类。

现在,是时候创建规则了。请注意,所有验证规则都只是继承自 Attribute 类并实现 IModelRule 接口的 Validate 方法的类。如果输入的文本超过指定的长度上限,图 4 中的长度上限规则返回错误。其他用于验证必填字段、电话和电子邮件地址字段格式的规则的工作方式类似,区别在于它们对要验证的数据类型采用不同的逻辑。

图 4:MaxLengthRule 类

public class MaxLengthRule : Attribute, IModelRule{private int _maxLength = 0;public MaxLengthRule(int maxLength) { _maxLength = maxLength; }public ValidationResult Validate(string fieldName, object fieldValue){    var message = $"Cannot be longer than {_maxLength} characters";if (fieldValue == null) { return new ValidationResult() { IsValid = true }; }    var stringvalue = fieldValue.ToString();if (stringvalue.Length > _maxLength )    {return new ValidationResult() { IsValid = false, Message = message };    }else    {return new ValidationResult() { IsValid = true };    }  }}

创建 Blazor 注册窗体

至此,验证引擎已在共享库中完成,它可以应用于 Blazor 应用程序中的新注册窗体。首先,我在 Blazor 应用程序中添加对共享库项目的引用。为此,可使用“引用管理器”对话框的“解决方案”窗口,如图 5 所示。

图 5:添加对共享库的引用

接下来,我向应用程序的 NavMenu 添加新导航链接。

打开SharedNavMenu.cshtml 文件,并向列表添加新注册窗体链接如图 6 所示。

图 6:添加注册窗体链接

<div class=@(collapseNavMenu ? "collapse" : null) onclick=@ToggleNavMenu><ul class="nav flex-column"><li class="nav-item px-3"><NavLink class="nav-link" href="" Match=NavLinkMatch.All><span class="oi oi-home" aria-hidden="true"></span> Home</NavLink></li><li class="nav-item px-3"><NavLink class="nav-link" href="counter"><span class="oi oi-plus" aria-hidden="true"></span> Counter</NavLink></li><li class="nav-item px-3"><NavLink class="nav-link" href="fetchdata"><span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data</NavLink></li><li class="nav-item px-3"><NavLink class="nav-link" href="registrationform"><span class="oi oi-list-rich" aria-hidden="true"></span> Registration Form</NavLink></li></ul></div>

最后,我在 Pages 文件夹中添加新 RegistrationForm.cshtml 文件。为此,可使用图 7 中的代码。

图 7 中的 cshtml 代码在 <form> 标记内有四个 <TextInput> 字段。<TextInput> 标记是自定义 Blazor 组件,用于处理字段的数据绑定和错误显示逻辑。此组件只需要三个参数即可正常运行:

  • Model 字段:标识数据要绑定到的类。

  • FieldName:标识数据要绑定到的数据成员。

  • DisplayName 字段:让组件可以显示易记消息。

图 7:添加 RegistrationForm.cshtml 文件

@page "/registrationform"@inject HttpClient Http@using SharedLibrary<h1>Registration Form</h1>@if (!registrationComplete){  <form>    <div class="form-group">      <TextInput Model="model" FieldName="FirstName" DisplayName="First Name" />    </div>    <div class="form-group">

      <TextInput Model="model" FieldName="LastName" DisplayName="Last Name" />

    </div>    <div class="form-group">      <TextInput Model="model" FieldName="Email" DisplayName="Email" />    </div>    <div class="form-group">      <TextInput Model="model" FieldName="Phone" DisplayName="Phone" />    </div>    <button type="button" class="btn btn-primary" onclick="@Register"      disabled="@model.HasErrors()">Register</button>  </form>}else{  <h2>Registration Complete!</h2>}@functions {bool registrationComplete = false;  RegistrationData model { get; set; }protected override void OnInit(){base.OnInit();    model = new RegistrationData() { FirstName ="test", LastName = "test", Email = "test@test.com", Phone = "1234567890" };    model.ModelChanged += ModelChanged;    model.CheckRules();  }private void ModelChanged(object sender, EventArgs e){base.StateHasChanged();  }async Task Register(){await Http.PostJsonAsync<RegistrationData>("https://localhost:44332/api/Registration", model);    registrationComplete = true;  }}

在页面的 @functions 块内,代码只有一点点。OnInit 方法使用其中的一些测试数据来初始化模型类。

它绑定到 ModelChanged 事件,并调用 CheckRules 方法来验证规则。

ModelChanged 处理程序调用 base.StateHasChanged 方法,以强制执行 UI 刷新。Register 方法在“注册”按钮获得单击时调用,并将注册数据发送到后端WebAPI 服务。

TextInput 组件包含输入标签、输入文本框、验证错误消息,以及在用户键入内容的同时更新模型的逻辑。Blazor 组件非常易于编写,并提供了将接口分解为可重用部分的强大方法。参数成员使用 Parameter 属性进行修饰,以便让 Blazor 知道它们是组件参数。

输入文本框的 oninput 事件连接到 OnFieldChanged 处理程序。每当输入更改,都会触发此事件。然后,OnFieldChanged 处理程序调用 SetValue 方法,以对相应字段执行规则,并在用户键入内容的同时实时更新错误消息。图 8 展示了代码。

图 8:更新错误消息

@using SharedLibrary<label>@DisplayName</label><input type="text" class="form-control" placeholder="@DisplayName"  oninput="@(e => OnFieldChanged(e.Value))"value="@Model.GetValue(FieldName)" /><small class="form-text" style="color:darkred;">@Model.Errors(FieldName)  </small>@functions {  [Parameter]  ModelBase Model { get; set; }  [Parameter]  String FieldName { get; set; }  [Parameter]  String DisplayName { get; set; }public void OnFieldChanged(object value){    Model.SetValue(FieldName, value);  }}

服务器上的验证

验证引擎现已开始在客户端上运行。下一步是在服务器上使用共享库和验证引擎。为此,我先向解决方案添加另一个 ASP.NET Core Web 应用程序项目。这次,我在图 1 所示的“新建 ASP.NET Core Web 应用程序”对话框中选择的是“API”,而不是“Blazor”。

新建 API 项目后,我就添加对共享项目的引用,就像在 Blazor 客户端应用程序中(见图 5)一样。接下来,我向 API 项目添加新控制器。

新控制器接受来自 Blazor 客户端的 RegistrationData 调用,如图 9 所示。注册控制器在服务器上运行,并且是后端 API 服务器的典型特征。区别在于,它现在运行在客户端上运行的相同验证规则。

图 9:注册控制器

[Route("api/Registration")][ApiController]public class RegistrationController : ControllerBase{  [HttpPost]public IActionResult Post([FromBody] RegistrationData value){if (value.HasErrors())    {return BadRequest();    }// TODO: Save data to databasereturn Created("api/registration", value);  }}

注册控制器有一个 POST 方法,它接受 RegistrationData 作为自己的值。它调用 HasErrors 方法,以验证所有规则并返回布尔值。

若有错误,控制器返回 BadRequest 响应;否则,它返回成功响应。我特意省略掉了将注册数据保存到数据库的代码,这样我就可以验证方案为重点了。

现在,共享验证逻辑在客户端和服务器上运行。

远景

此简单示例展示了如何在浏览器和后端之间共享验证逻辑,仅仅触及全栈 C# 环境强大功能的皮毛。

Blazor 的神奇之处在于,使用它,现有 C# 开发人员大军可以生成功能强大的新式响应式单页应用程序,且最大限度地缩短启动时间。使用它,企业可以重用和重新打包现有代码,以便能够直接在浏览器中运行现有代码。

能够在浏览器、桌面、服务器、云和移动平台之间共享 C# 代码,将大大提升开发人员的工作效率。它还便于开发人员更快地向客户交付更多功能和更多业务价值。

原文地址:https://msdn.microsoft.com/zh-cn/magazine/mt833288

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

使用 C# 和 Blazor 进行全栈开发相关推荐

  1. 扶贫干部拍胸脯认证,AI开发者上手零门槛,百度打造 “云智一体”全栈开发杀手锏...

    "我可以拍着胸脯说识别准确率很高."扶贫干部刘乐这样评价他在使用百度EasyDL平台助力扶贫的效果,他是陕西省汉中市扶贫信息中心副主任,也是一名热爱编程的程序员. 在近期百度智能云 ...

  2. mybatis mapper.xml dtd_全栈开发踩坑之路4-用MyBatis实现服务

    1.前言 上一篇文章介绍了如何设计后端的Mysql数据库:Alex Wang:全栈开发踩坑之路3-MySql数据库设计,本文介绍如何用MyBatis实现后端服务. 本后端项目的Github地址(撰写中 ...

  3. 预告:Javascript全栈开发的系列文章

    自从一年前发布了Vuejs小书的电子书,也有些日子没有碰过它们了,现在因为项目的缘故,需要使用JavaScript全栈开发.所以,我得把这个全栈环境搭建起来. 说起来搭建JS全栈开发环境,设计到的东西 ...

  4. 《JavaScript快速全栈开发》作者Azat Mardanov:现在是拥抱Node技术栈的最佳时机

    非商业转载请注明作译者.出处,并保留本文的原始链接:http://www.ituring.com.cn/article/195742 Azat Mardanov是一位有着12年开发经验的资深软件工程师 ...

  5. 从编程小白到全栈开发:基于框架开发服务端

    上文中,我们了解了关于服务器端的一些概念知识,尤其是HTTP协议相关的最基本知识点,今天我想跟大家分享一下在平时正真的开发中,是如何来利用和体现这些内容的. 还记得我在<从编程小白到全栈开发:改 ...

  6. PYTHON高级全栈开发工程师-老男孩教育

    PYTHON高级全栈开发工程师 最近开班日期:2016年4月17号                               课程周期:4至4.5个月 学习方式:全脱产面授学习(周一至周五,早9:3 ...

  7. vue java 插件开发_实习模块vue+java小型全栈开发(三)

    实习模块vue+java小型全栈开发(三) --dx 背景 首先,先给自己一个答案:这篇博客我定义为(三),因为之前的两个模块页面,内容都是一样的,但是被改了几次需求,就一直拖着没有上传. 今天是真正 ...

  8. python利器手机版-将安卓手机打造成你的python全栈开发利器

    超神利器- 相信多数安卓用户都使用过Qpython这款移动端的Python编辑器吧?之前我也研究过一阵子这个工具,但因为一次简单的爬虫让我对它失望之极.Qpython不支持lxml这个模块,然而pyt ...

  9. Flask Vue.js全栈开发

    Flask Vue.js全栈开发的 最新完整代码 及使用方式 本系列的最新代码及使用方式将持续更新到: http://www.madmalls.com/blog/post/latest-code/ 1 ...

最新文章

  1. 04号团队-团队任务5:项目总结会
  2. 别再纠结线程池大小/线程数量了,没有固定公式的
  3. 博客园博客账号意外被封怎么办?
  4. 框架:@Bean注解
  5. android 内部拦截,如何使用proxy,如何在内部拦截get方法
  6. Web安全通讯之JWT的Java实现
  7. java 字符串 常量_Java进阶——Java中的字符串常量池
  8. 百度地图隐藏LOGO显示
  9. php urledcode_用JavaScript实现PHP的urldecode函数
  10. web charset
  11. [Linux笔记]重装windows后重装grub
  12. 客户端session与服务端session
  13. Rust游戏数据查询、Rust服务器清档时间表
  14. JAVA日本图片_java使用Thumbnailator操作图片
  15. 高通efs_了解EFS
  16. 分布式服务治理及优化经验
  17. Smart Thief 问题
  18. 为什么要用深浅拷贝、什么是深浅拷贝、以及如何实现
  19. 计算机网络p2p应用,[计算机网络-应用层] P2P应用
  20. 【Mysql】数据库的设计学习笔记

热门文章

  1. 如何在Photoshop中制作双曝光图像
  2. 使用putty连接linux
  3. POJ 3080 Blue Jeans (后缀数组)
  4. 并发编程总结4-JUC-REENTRANTLOCK-2(公平锁)
  5. 使用ssh tunnel 来做代理或跳板
  6. 2.页面布局示例笔记
  7. centos linux 禁止ping
  8. Unix操作系统***追踪反击战
  9. 神奇的[Caller*]属性
  10. C# params的用法详解