您现在的位置是:首页 >技术教程 >第14章后端验证中间件使用场景与选择网站首页技术教程
第14章后端验证中间件使用场景与选择
.Net框架前端输入的后端常用验证中间件有DataAnnotations、FluentValidation两种可供选择使用。
FluentValidation:
解耦了模型类定义与验证定义。
解耦了验证实现与控制器类的定义。
可以通过泛型方式方法和具体为特定的模型类的验证实现进行定义,有利于验证的统一定义和管理。
可以泛型方式把所有的验证实现类的实例进行注入例如:
var typeFinder = Singleton<ITypeFinder>.Instance;
var consumers1 = typeFinder.FindClassesOfType(typeof(IValidator<>)).ToList();
foreach (var consumer in consumers1)
foreach (var findInterface in consumer.FindInterfaces((type, criteria) =>
{
var isMatch = type.IsGenericType && ((Type)criteria).IsAssignableFrom(type.GetGenericTypeDefinition());
return isMatch;
}, typeof(IValidator<>)))
builder.Services.AddScoped(findInterface, consumer);
异步定义实现,不支持自动验证。
学习和使用成本都很高,如非必须不管理是Web Mvc 还是Web Api 都建议使用
其是第3方验证中间件,如果前端是Razor页面那么就不要考虑使用
DataAnnotations
验证的定义实现与模型类和控制器类的定义实现紧密耦合。
其是内置中间件,如果前端是Razor页面,那么就是它了。
与Razor页面配合能够足够简单的完成自动验证。
自动验证:
- 在输入完成并失去焦点后,立即渲染显示验证信息。
- 在单击提交按钮后,便未跳转到指定的控制器行为方法,渲染显示验证信息。
手动验证:
单击提交按钮后,跳转到指定的控制器行为方法中,并在该方法中进行验证,如果未通过验证,那么在前端
渲染显示验证信息例如:
if (_customerSettings.UsernamesEnabled && await _customerService.GetCustomerByUsernameAsync(request.Username) != null)
{
result.AddError(await _localizationService.GetResourceAsync("Account.Register.Errors.UsernameAlreadyExists"));
return result;
}
if (!string.IsNullOrWhiteSpace(model.Email) && await _customerService.GetCustomerByEmailAsync(model.Email) != null)
ModelState.AddModelError(string.Empty, "Email is already registered");
if (ModelState.IsValid)
1 Web.Areas.Admin.Models.Customers.RoleModel
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
namespace Web.Areas.Admin.Models.Customers
{
/// <summary>
/// 【角色模型--纪录】
/// <remarks>
/// 摘要:
/// 为角色添加/编辑Razor页面输入和验证的渲染显示提供数据支撑。
/// </remarks>
/// </summary>
public record RoleModel
{
/// <summary>
/// 【编号】
/// <remarks>
/// 摘要:
/// 获取/设置角色实体实例的长整型编号值。
/// </remarks>
/// </summary>
[Display(Name = "编号")]
public long Id { get; set; }
/// <summary>
/// 【角色名】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定的角色名。
/// 说明:
/// 不区分大小写且必须具有唯一性。
/// </remarks>
/// </summary>
[Display(Name = "名称")]
[Required(ErrorMessage = "必须输入角色名。")]
[Remote(action: "UniqueName", controller: "Role", AdditionalFields = nameof(Id))]
[MaxLength(255)]
public string Name { get; set; }
/// <summary>
/// 【启用?】
/// <remarks>
/// 摘要:
/// 获取/设置1个值false(禁用)/true(默认值:启用),该值指示角色实体的1个指定实例是否处于启用状态。
/// </remarks>
/// </summary>
[Display(Name = "可用")]
public bool Active { get; set; }
/// <summary>
/// 【系统角色?】
/// <remarks>
/// 摘要:
/// 获取/设置1个值false(不是系统角色)/true(默认值:系统角色),该值指示角色实体的1个指定实例是否是系统角色
/// 说明:
/// 名称不能被修改;不能被物理/逻辑删除;必须处于启用状态。
/// </remarks>
/// </summary>
[Display(Name = "系统角色")]
public bool IsSystemRole { get; set; }
/// <summary>
/// 【备注】
/// <remarks>
/// 摘要:
/// 获取/设置1个指定角色的备注信息。
/// </remarks>
/// </summary>
[Display(Name = "备注")]
[MaxLength(500)]
public string Remark { get; set; }
}
}
2 Web.Areas.Admin.Controllers.RoleController
using Core.Domain.Customers;
using Microsoft.AspNetCore.Mvc;
using Services.Customers;
using Web.Areas.Admin.Models.Customers;
namespace Web.Areas.Admin.Controllers
{
[Area("Admin")]
public class RoleController : Controller
{
#region 拷贝构造方法与变量
private readonly ICustomerService _customerService;
/// <summary>
/// 【拷贝构建方法】
/// <remarks>
/// 摘要:
/// 依赖注入容器通过拷贝构造方法,实例化该类中的变量成员。
/// </remarks>
/// </summary>
public RoleController(ICustomerService customerService)
{
_customerService = customerService;
}
#endregion
#region Razor页面中输入的自动验证
/// <param name="name">被验证的角色名。</param>
/// <param name="id">被验证角色名所对应的长整型编号值,默认值:0,用户角色实例的新建。</param>
/// <summary>
/// 【唯一角色名】
/// <remarks>
/// 摘要:
/// 通过相应的参数实例,远程验证表单中所输入的用户名是否已经被注册。
/// </remarks>
/// <returns>
/// 返回:
/// JSON编码格式的验证结果状态信息。
/// </returns>
/// </summary>
public async Task<IActionResult> UniqueName(string name, long id = 0)
{
Role _role = (await _customerService.GetAllRolesAsync(true)).Where(role => role.Name.ToLower().Equals(name.Trim().ToLower())).FirstOrDefault();
if (_role != null && _role.Id != id)
return Json($"该角色名已经被使用。");
return Json(true);
}
#endregion
#region CURD
/// <summary>
/// 【角色列表】
/// <remarks>
/// 摘要:
/// 获取角色实体的所有实例,为角色列表Razor页面的渲染显示提供数据支撑。
/// </remarks>
/// <returns>
/// 1个列表实例,该实例存储着角色实体的所有实例。
/// </returns>
/// </summary>
public async Task<IActionResult> Index()
{
IList<Role> _roleList = await _customerService.GetAllRolesAsync(true);
return View(_roleList);
}
/// <summary>
/// 【添加角色】
/// <remarks>
/// 摘要:
/// 获取角色实体的1个空实例,为添加角色Razor页面的渲染显示提供数据支撑。
/// </remarks>
/// <returns>
/// 返回:
/// 角色实体的1个空实例。
/// </returns>
/// </summary>
public IActionResult Create()
{
return View();
}
/// <param name="model">角色模型记录的1个指定实例。</param>
/// <summary>
/// 【添加角色】
/// <remarks>
/// 摘要:
/// 通过添加角色Razor页面中的数据,把角色实体的1个指定实例持久化到角色表中。
/// </remarks>
/// <returns>
/// 返回:
/// 角色模型记录的1个指定实例。
/// </returns>
/// </summary>
[HttpPost]
public async Task<IActionResult> Create(RoleModel model)
{
var role = new Role() { Name = model.Name, Active = true, IsSystemRole = model.IsSystemRole, Remark = model.Remark };
await _customerService.InsertRoleAsync(role);
ViewBag.RefreshPage = true;
return View(model);
}
/// <param name="id">1个指定的长整型编号值。</param>
/// <summary>
/// 【修改角色】
/// <remarks>
/// 摘要:
/// 获取角色实体的1个指定实例,为修改角色Razor页面的渲染显示提供数据支撑。
/// </remarks>
/// <returns>
/// 返回:
/// 角色模型记录的1个指定实例。
/// </returns>
/// </summary>
public async Task<IActionResult> Edit(long id)
{
var role = await _customerService.GetRoleByIdAsync(id);
var model = new RoleModel() { Id = role.Id, Name = role.Name, Active = role.Active, IsSystemRole = role.IsSystemRole, Remark = role.Remark };
return View(model);
}
/// <param name="model">角色模型记录的1个指定实例。</param>
/// <summary>
/// 【修改角色】
/// <remarks>
/// 摘要:
/// 通过更新角色Razor页面中的数据,把角色实体的1个指定实例持久化更新到角色表中。
/// </remarks>
/// <returns>
/// 返回:
/// 角色模型记录的1个指定实例。
/// </returns>
/// </summary>
[HttpPost]
public async Task<IActionResult> Edit(RoleModel model)
{
var role = new Role() { Id = model.Id, Name = model.Name, Active = model.Active, IsSystemRole = model.IsSystemRole, Remark = model.Remark };
await _customerService.UpdateRoleAsync(role);
ViewBag.RefreshPage = true;
return View(model);
}
#endregion
}
}
3 WebAreasAdminViewsRoleIndex.cshtml
@model IEnumerable<Core.Domain.Customers.Role>
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<div class="fs-3">角色列表</div>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-end">
<li class="breadcrumb-item">
@*resetRoleList:用于控制对弹出框的关闭和角色列表的刷新渲染*@
<button type="button" id="resetRoleList" class="btn btn-secondary me-3 d-none">
<i class="fa-solid fa-rotate-right"></i>
重置
</button>
<button type="button" onclick="OpenWindow('@(Url.Action("Create", "Role", new {btnId = "resetRoleList"}))', 500, 500, true); return false;" class="btn btn-success float-end">
<i class="fas fa-plus"></i>
添加
</button>
</li>
</ol>
</div>
</div>
</div>
</div>
<!-- Main content -->
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<!-- Default box -->
<div class="card">
<div class="card-body">
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 100px">
编号
</th>
<th>
名称
</th>
<th style="width: 50px">
可用
</th>
<th style="width: 50px">
系统
</th>
<th>
备注
</th>
<th class="text-center" style="width: 100px">
操作
</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Id)
</td>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td class="text-center">
@if (item.Active)
{
<i class="fas fa-check fa-solid fa-bold fa-xl true-icon text-success"></i>
}
else
{
<i class="fas fa-times fa-solid fa-bold fa-xl false-icon text-danger"></i>
}
</td>
<td class="text-center">
@if (item.IsSystemRole)
{
<i class="fas fa-check fa-solid fa-bold fa-xl true-icon text-success"></i>
}
else
{
<i class="fas fa-times fa-solid fa-bold fa-xl false-icon text-danger"></i>
}
</td>
<td>
@Html.DisplayFor(modelItem => item.Remark)
</td>
<td class="text-center">
<button type="button" onclick="OpenWindow('@Url.Content("~/Admin/Role/Edit/")' + @item.Id + '?btnId=resetRoleList' ,500, 500, true); return false;" class="btn btn-info btn-sm">
<i class="fas fa-pencil-alt"></i>
编辑
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<!-- /.card -->
</div>
</div>
<!-- /.row -->
</div><!-- /.container-fluid -->
</div>
<!-- /.content -->
@section Scripts {
<script type="text/javascript">
$(document).ready(function () {
//console.log('ready!');
console.log(1111);
//alert("删除学生的编号");
});
$("#resetRoleList").on("click", function () {
window.location.href = "/Admin/Role/Index";
});
</script>
}
4 WebAreasAdminViewsRoleCreate.cshtml
@model RoleModel
@{
ViewData["Title"] = "添加角色";
Layout = "~/Areas/Admin/Views/Shared/_LayoutPopup.cshtml";
}
@if (ViewBag.RefreshPage == true)
{
<script type="text/javascript">
//如果重置按钮定义在查询表单中时,通过下1行语句实现对弹出框的关闭。
//window.opener.document.forms['@(Context.Request.Query["formId"])'].@(Context.Request.Query["btnId"]).click();
//如果重置按钮单独定义时,通过下1行语句实现对弹出框的关闭。
window.opener.document.getElementById("@Context.Request.Query["btnId"]").click();
window.close();
</script>
}
<div class="content-header">
</div>
<!-- Main content -->
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card card-primary card-outline">
<form asp-action="Create"
asp-route-btnId="@Context.Request.Query["btnId"]">
<div class="card-body">
<div class="mb-3">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="mb-3">
<label class="form-check-label">
<input class="form-check-input check25px" asp-for="IsSystemRole" />
<label class="check25pxLabel">
@Html.DisplayNameFor(model => model.IsSystemRole)
</label>
</label>
</div>
<div class="fmb-3">
<label asp-for="Remark" class="control-label"></label>
<textarea asp-for="Remark" class="form-control" rows="3"></textarea>
<span asp-validation-for="Remark" class="text-danger"></span>
</div>
<div class="card-footer text-center">
<input type="submit" value="保存" class="btn btn-primary" />
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
5 WebAreasAdminViewsRoleEdit.cshtml
@model RoleModel
@{
ViewData["Title"] = "修改角色";
Layout = "~/Areas/Admin/Views/Shared/_LayoutPopup.cshtml";
}
@if (ViewBag.RefreshPage == true)
{
<script type="text/javascript">
//如果重置按钮定义在查询表单中时,通过下1行语句实现对弹出框的关闭。
//window.opener.document.forms['@(Context.Request.Query["formId"])'].@(Context.Request.Query["btnId"]).click();
//如果重置按钮单独定义时,通过下1行语句实现对弹出框的关闭。
window.opener.document.getElementById("@Context.Request.Query["btnId"]").click();
window.close();
</script>
}
<div class="content-header">
</div>
<!-- Main content -->
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card card-primary card-outline">
<form asp-action="Edit"
asp-route-btnId="@Context.Request.Query["btnId"]">
<div class="card-body">
<div class="d-none">
<input asp-for="Id" class="form-control" />
</div>
<div class="mb-3">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="mb-3">
<label class="form-check-label">
<input class="form-check-input check25px" asp-for="Active" />
<label class="check25pxLabel">
@Html.DisplayNameFor(model => model.Active)
</label>
</label>
</div>
<div class="mb-3">
<label class="form-check-label">
<input class="form-check-input check25px" asp-for="IsSystemRole" />
<label class="check25pxLabel">
@Html.DisplayNameFor(model => model.IsSystemRole)
</label>
</label>
</div>
<div class="fmb-3">
<label asp-for="Remark" class="control-label"></label>
<textarea asp-for="Remark" class="form-control" rows="3"></textarea>
<span asp-validation-for="Remark" class="text-danger"></span>
</div>
<div class="card-footer text-center">
<input type="submit" value="保存" class="btn btn-primary" />
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
对以上功能更为具体实现和注释见230521_014ShopRazor(Role CURD Popup定义实现)。