第24章 DataAnnotations自定义手动验证的定义实现

1 Services.Customers.CustomerService.GetCustomerByEmailAsync

/// <param name="email">1个指定的电子邮箱字符串。</param>

        /// <summary>

        /// 【异步通过电子邮箱获取用户】

        /// <remarks>

        /// 摘要:

        ///    直接从用户表中获用户实体的1个指定实例。

        /// 说明:

        ///    该方法没有定义缓存操作,更没有定义缓存的移除操作。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///    用户实体的1个指定实例。

        /// </returns>

        /// </summary>

        public virtual async Task<Customer> GetCustomerByEmailAsync(string email)

        {

            if (string.IsNullOrWhiteSpace(email))

                return null;

            var query = from c in _customerRepository.Table

                        orderby c.Id

                        where c.Email == email

                        select c;

            var customer = await query.FirstOrDefaultAsync();

            return customer;

        }

2 Services.Customers.CustomerService.GetCustomerByPhoneAsync

/// <param name="phone">1个指定的手机号。</param>

        /// <summary>

        /// 【异步通过手机号获取用户】

        /// <remarks>

        /// 摘要:

        ///    直接从用户表中获用户实体的1个指定实例。

        /// 说明:

        ///    该方法没有定义缓存操作,更没有定义缓存的移除操作。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///    用户实体的1个指定实例。

        /// </returns>

        /// </summary>

        public virtual async Task<Customer> GetCustomerByPhoneAsync(string phone)

        {

            if (string.IsNullOrWhiteSpace(phone))

                return null;

            var query = from c in _customerRepository.Table

                        orderby c.Id

                        where c.Phone == phone

                        select c;

            var customer = await query.FirstOrDefaultAsync();

            return customer;

        }

3 Services.Customers.CustomerService.InsertCustomerAsync

 /// <param name="customer">用户实体的1个指定实例。</param>

        /// <summary>

        /// 【异步插入用户】

        /// <remarks>

        /// 摘要:

        ///     把用户实体的1个指定实例持久化插入到用户表中后,并从缓存数据库中移除与用户相关的所有缓存项。

        /// </remarks>

        /// </summary>

        public virtual async Task InsertCustomerAsync(Customer customer)

        {

            await _customerRepository.InsertAsync(customer);

        }

4 Services.Customers.CustomerService.UpdateCustomerAsync

 /// <param name="customer">用户实体的1个指定实例。</param>

        /// <summary>

        /// 【异步更新用户】

        /// <remarks>

        /// 摘要:

        ///     把用户实体的1个指定实例持久化更新到用户表中后,并从缓存数据库中移除与用户实体相关的所有缓存项。

        /// </remarks>

        /// </summary>

        public virtual async Task UpdateCustomerAsync(Customer customer)

        {

            await _customerRepository.UpdateAsync(customer);

        }

5 Services.Customers.CustomerService.DeleteCustomerAsync

 /// <param name="customer">用户实体的1个指定实例。</param>

        /// <summary>

        /// 【异步逻辑删除用户】

        /// <remarks>

        /// 摘要:

        ///     把用户实体的1个指定实例从用户表中逻辑删除后,并从缓存数据库中移除与用户实体相关的所有缓存项。

        /// </remarks>

        /// </summary>

        public virtual async Task DeleteCustomerAsync(Customer customer)

        {

            if (customer == null)

                throw new ArgumentNullException(nameof(customer));

            customer.Deleted = true;

            await _customerRepository.UpdateAsync(customer, false);

            await _customerRepository.DeleteAsync(customer);

        }

6 Services.Customers.CustomerService.AddCustomerRoleMappingAsync

    /// <param name="customerRoleMapping">用户角色映射实体的1个指定实例。</param>

        /// <summary>

        /// 【异步插入用户角色映射】

        /// <remarks>

        /// 摘要:

        ///     把用户角色映射实体的1个指定实例持久化插入到用户角色映射表中后,并从缓存数据库中移除与用户角色映射相关的所有缓存项。

        /// </remarks>

        /// </summary>

        public async Task AddCustomerRoleMappingAsync(CustomerRoleMapping customerRoleMapping)

        {

            await _customerRoleMappingRepository.InsertAsync(customerRoleMapping);

        }

7 Services.Customers.CustomerService.RemoveCustomerRoleMappingAsync

/// <param name="customer">用户实体的1个指定实例。</param>

        /// <param name="role">角色实体的1个指定实例。</param>

        /// <summary>

        /// 【异步物理删除用户角色映射】

        /// <remarks>

        /// 摘要:

        ///     把用户角色映射实体的1个指定实例从用户角色映射表中物理删除后,并从缓存数据库中移除与用户角色映射实体相关的所有缓存项。

        /// </remarks>

        /// </summary>

        public async Task RemoveCustomerRoleMappingAsync(Customer customer, Role role)

        {

            if (customer is null)

                throw new ArgumentNullException(nameof(customer));

            if (role is null)

                throw new ArgumentNullException(nameof(role));

            var mapping = await _customerRoleMappingRepository.Table

                .SingleOrDefaultAsync(ccrm => ccrm.CustomerId == customer.Id && ccrm.RoleId == role.Id);

            if (mapping != null)

                await _customerRoleMappingRepository.DeleteAsync(mapping);

        }

8 重构Web.Areas.Admin.Models.Customers.CustomerModel

using Framework.Models;

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.Rendering;

using System.ComponentModel.DataAnnotations;

namespace Web.Areas.Admin.Models.Customers

{

    /// <summary>

    /// 【用户模型--纪录】

    /// <remarks>

    /// 摘要:

    ///     为用户JQuery DataTables表格、添加和编辑Razor页面输入和验证的渲染显示提供数据支撑。

    /// </remarks>

    /// </summary>

    public record CustomerModel : BaseEntityModel

    {

        #region 拷贝构造方法

        /// <summary>

        /// 【拷贝构造方法】

        /// <remarks>

        /// 摘要:

        ///     实例化该纪录时,为列表接口类型的属性成员实例分配内存空间。

        /// </remarks>

        /// </summary

        public CustomerModel()

        {

            SelectedRoleIds = new List<long>();

            AvailableRoles = new List<SelectListItem>();

        }

        #endregion

        #region 属性

        /// <summary>

        /// 【用户名】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定的用户名(账户、昵称)

        /// </remarks>

        /// </summary>

        [Display(Name = "用户名")]

        [Required(ErrorMessage = "必须输入用户名。")]

        public string Username { get; set; }

        /// <summary>

        /// 【电子邮箱】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户所对应的电子邮箱。

        /// </remarks>

        /// </summary>

        [Display(Name = "邮箱")]

        [Required(ErrorMessage = "必须输入电子邮箱。")]

        [EmailAddress(ErrorMessage = "输入的电子邮件格式错误。")]

        [Remote(action: "UniqueEmail", controller: "Customer", AdditionalFields = nameof(Id))]

        public string Email { get; set; }

        /// <summary>

        /// 【身份证号】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户所对应的身份证号。

        /// </remarks>

        /// </summary>

        //public string IdCard { get; set; }

        /// <summary>

        /// 【实名】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户所对应的实名。

        /// </remarks>

        /// </summary>

        //public string RealName { get; set; }

        /// <summary>

        /// 【密码】

        /// <remarks>

        /// 摘要:

        ///    获取/设置经过指定加密方式加密后的密码字符串。

        /// </remarks>

        /// </summary>

        [Display(Name = "密码")]

        [DataType(DataType.Password)]

        public string Password { get; set; }

        /*

        可逆加密(Encryption)方式和哈希(Hash)加密方式的区别:

           1、可逆加密(Encryption)是可逆的,即明码和加密码之间通过操作是可以相互转换;哈希(Hash)加密方式是不可逆的,哈希(Hash)加密方式一般会导致信息熵减小,即使用哈希(Hash)加密方式转换后可能导致,转换明码是原明码中的一段。

           2、可逆加密(Encryption)的密码会随着明码的长度进行改变;而哈希(Hash)加密方式密码的长度是固定的,且只取决于所使用的算法,当明码的长度大于算法中所规则的长度时,哈希(Hash)加密方式会把明码截断后进行加密操作,这也是导致哈希(Hash)加密方式不可逆的与信息熵减小的根本原因。

           3、应用场景:一般情况下用户密码加密操作使用可逆加密(Encryption)方式;哈希(Hash)加密方式(一般用于数字签名、数据校验(CRCSHAMD5),据说HTTPS协议运行所需要的CA证书就是使用哈希(Hash)算法生成的。

           4、哈希(Hash)加密方式无解密操作,即哈希(Hash)加密方式是不能被解密的,如果用户输入的密码经过哈希(Hash)加密方式加密后的加密字符串与原加密字符相等,则用户通过验证,可以登录了。

        */

        /// <summary>

        /// 【哈希密钥】

        /// <remarks>

        /// 摘要:

        ///     获取/设置哈希(Hash)加密方式所需的密钥字符串(默认:5个字符的字符串)

        ///  说明:

        ///     使用哈希加密方式对用户的密进行加密操作时,所使用的密钥字符串。

        /// </remarks>

        /// </summary>

        public string PasswordSalt { get; set; }

        /// <summary>

        /// 【性别】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户所对应的性别。

        /// </remarks>

        /// </summary>

       // public int Gender { get; set; }

        /// <summary>

        /// 【手机号】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户所对应的手机号。

        /// </remarks>

        /// </summary>

        [Display(Name = "手机号")]

        [RegularExpression(@"^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$", ErrorMessage = "输入的手机号格式错误。")]

        [Remote(action: "UniquePhone", controller: "Customer", AdditionalFields = nameof(Id))]

        public string Phone { get; set; }

        /// <summary>

        /// 【头像】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户所对应的头像图片网络格式的路径字符串。

        ///  说明:

        ///     1、本地为网络格式的相对路径字符串。

        ///     2、第3方云端为网络格式的绝对路径字符串。

        /// </remarks>

        /// </summary>

        public string Avatar { get; set; }

        /// <summary>

        /// 【登录失败次数】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户执行同1次登录操作时密码输入错误的次数。

        /// 说明:

        ///     用于纪录密码输入错误的次数,在用户输入的密码错误次数超过限定值时,设定1个指定的时间值,在该时间值内,用户将用法执行登录操作(通过直接重定向到登出操作)

        /// </remarks>

        /// </summary>

        // public int FailedLoginAttempts { 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 Deleted { get; set; }

        /// <summary>

        /// 【系统帐户?】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个值false(默认值:不是)/true(),该值指示用户实体的1个指定实例是否是系统帐户。

        /// 说明:

        ///     如果是系统帐户,则该用户将不能被物理/逻辑删除,即在删除操作中会通过该属性实例过滤掉系统帐户的所有用户。

        /// </remarks>

        /// </summary>

        [Display(Name = "系统帐户")]

        public bool IsSystemAccount { get; set; }

        /// <summary>

        /// 【最后IP地址】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户最后1次执行登录操作的IP地址。

        /// </remarks>

        /// </summary>

       // public string LastIpAddress { get; set; }

        /// <summary>

        /// 【生日】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户的生日。

        /// </remarks>

        /// </summary>

        //public DateTime? DateOfBirth { get; set; }

        /// <summary>

        /// 【最后登录时间】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户最后1次执行登录操作的时间。

        /// </remarks>

        /// </summary>

        //public DateTime? LastLoginDate { get; set; }

        /// <summary>

        /// 【最后活动时间】

        /// <remarks>

        /// 摘要:

        ///     获取/设置用户实体1个指定实例最后一次操作的时间。

        /// 说明:

        ///     在默认情况下每间隔20分钟,如果已经登录的用户有操作则对用户实体1个指定实例最后一次操作的时间进行更新。

        /// </remarks>

        /// </summary>

        //public DateTime LastActivityDate { get; set; }

        /// <summary>

        /// 【无法登录时间】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户无法登录的时间。

        /// 说明:

        ///     在用户输入的密码错误次数超过限定值时,设定1个指定的时间值,在该时间值内,用户将用法执行登录操作(通过直接重定向到登出操作)

        /// </remarks>

        /// </summary>

        //public DateTime? CannotLoginUntilDate { get; set; }

        /// <summary>

        /// 【创建时间】

        /// <remarks>

        /// 摘要:

        ///     获取/设置用户实体1个指定实例第1次被持久化到用户表中的时间。

        /// </remarks>

        /// </summary>

        public DateTime CreatedDate { get; set; }

        /// <summary>

        /// 【角色名集】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户的所对应所有角色的角色名。

        /// 说明:

        ///     角色名之间用“,”进行分割,并把所有角色名通过JSON格式进行编码。

        /// </remarks>

        /// </summary>

        public string RoleNames { get; set; }

        /// <summary>

        /// 【角色编号集】

        /// <remarks>

        /// 摘要:

        ///     获取/设置1个指定用户的所对应所有角色的角色名。

        /// 说明:

        ///     角色名之间用“,”进行分割,并把所有角色名通过JSON格式进行编码。

        /// </remarks>

        /// </summary>

        [Display(Name = "角色")]

        public IList<long> SelectedRoleIds { get; set; }

        public IList<SelectListItem> AvailableRoles { get; set; }

        #endregion

    }

}

9 Web.Areas.Admin.Controllers.CustomerController.UniqueEmail

 /// <param name="email">被验证的电子邮箱。</param>

        /// <param name="id">被验证用户实例所对应的长整型编号值,默认值:0,新的用户实例。</param>

        /// <summary>

        /// 【唯一电子邮箱】

        /// <remarks>

        /// 摘要:

        ///     通过相应的参数实例,远程验证表单中所输入的电子邮箱是否已经被注册。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     JSON编码格式的验证结果状态信息。

        /// </returns>

        /// </summary>

        public async Task<IActionResult> UniqueEmail(string email, long id = 0)

        {

            Customer customer = await _customerService.GetCustomerByEmailAsync(email.Trim());

            if (customer != null && customer.Id != id)

                return Json($"该电子邮箱已经被使用。");

            return Json(true);

        }

10 Web.Areas.Admin.Controllers.CustomerController.UniquePhone

        /// <param name="phone">被验证的手机号。</param>

        /// <param name="id">被验证用户实例所对应的长整型编号值,默认值:0,新的用户实例。</param>

        /// <summary>

        /// 【唯一手机号】

        /// <remarks>

        /// 摘要:

        ///     通过相应的参数实例,远程验证表单中所输入的手机号是否已经被注册。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     JSON编码格式的验证结果状态信息。

        /// </returns>

        /// </summary>

        public async Task<IActionResult> UniquePhone(string phone, long id = 0)

        {

            var v = (await _customerService.GetAllCustomersAsync(true)).ToList();

            Customer customer = await _customerService.GetCustomerByPhoneAsync(phone.Trim());

            if (customer != null && customer.Id != id)

                return Json($"该手机号已经被使用。");

            return Json(true);

        }

11 Web.Areas.Admin.Controllers.CustomerController.Create

 /// <param name="model">用户模型记录的1个指定实例。</param>

        /// <summary>

        /// 【添加用户】

        /// <remarks>

        /// 摘要:

        ///     通过添加用户Razor页面中的数据,把用户实体的1个指定实例持久化到用户表中。

        /// </remarks>

        /// <returns>

        /// 返回:

        ///     用户模型记录的1个指定实例。

        /// </returns>

        /// </summary>

        [HttpPost]

        public async Task<IActionResult> Create(CustomerModel model)

        {

            //后端手动对角色多选下拉框中的输入进行验证。

            if (model.SelectedRoleIds.Count <= 0)

            {

                ModelState.AddModelError("SelectedRoleIds", "必须选择1个角色。");

            }

            if (ModelState.IsValid)

            {

                var customer = model.ToEntity<Customer>();

                var saltKey = _encryptionService.CreateSaltKey(CustomerServicesDefaults.PasswordSaltKeySize);

                customer.PasswordSalt = saltKey;

                customer.Password = _encryptionService.CreatePasswordHash("1", saltKey, CustomerServicesDefaults.DefaultHashedPasswordFormat);

                customer.Active = true;

                customer.CreatedDate = DateTime.Now;

                await _customerService.InsertCustomerAsync(customer);

                var allRoles = await _customerService.GetAllRolesAsync(true);

                var newRoles = new List<Role>();

                foreach (var role in allRoles)

                    if (model.SelectedRoleIds.Contains(role.Id))

                        newRoles.Add(role);

                foreach (var role in newRoles)

                {

                    await _customerService.AddCustomerRoleMappingAsync(new CustomerRoleMapping { CustomerId = customer.Id, RoleId = role.Id });

                }

                ViewBag.RefreshPage = true;

            }

            model = await _customerModelFactory.PrepareCustomerModelAsync(model, null, true);

            return View(model);

        }

12 重构Web\Areas\Admin\Views\Customer\Index.cshtml

    //重置表格

        $("#resetCustomerList").on("click", function () {

            //重新加载DataTable插件,并返回第1页。

            window.location.href = "/Admin/Customer/Index";

        });

13 重构Web\Areas\Admin\Views\Customer\Create.cshtml

@model CustomerModel

@{

    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="Username" class="control-label"></label>

                                <input asp-for="Username" class="form-control" />

                                <span asp-validation-for="Username" class="text-danger"></span>

                            </div>

                            <div class="mb-3">

                                <label asp-for="Email" class="control-label"></label>

                                <input asp-for="Email" class="form-control" />

                                <span asp-validation-for="Email" class="text-danger"></span>

                            </div>

                            <div class="mb-3">

                                <label asp-for="Phone" class="control-label"></label>

                                <input asp-for="Phone" class="form-control" />

                                <span asp-validation-for="Phone" class="text-danger"></span>

                            </div>

                            <div class="mb-3">

                                <label class="form-check-label">

                                    <input class="form-check-input checkBox25" asp-for="IsSystemAccount" />

                                    <label class="checkBox25Label">

                                        @Html.DisplayNameFor(model => model.IsSystemAccount)

                                    </label>

                                </label>

                            </div>

                            <div class="mb-3">

                                <label asp-for="SelectedRoleIds" class="control-label"></label>

                                <select asp-for="SelectedRoleIds" asp-items="Model.AvailableRoles" asp-multiple="true" class="k-dropdownlist k-picker k-picker-md k-picker-solid k-rounded-md"></select>

                                <span asp-validation-for="SelectedRoleIds" class="text-danger"></span>

                            </div>

                        </div>

                        <div class="card-footer text-center">

                            <input type="submit" value="保存" class="btn btn-primary" />

                        </div>

                    </form>

                </div>

            </div>

        </div>

    </div>

</div>

@section Scripts {

    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}

    <script>

        $(document).ready(function () {

            $("#SelectedRoleIds").kendoMultiSelect({

                select: function (e) {

                    var current = this.value();

                    if (this.dataSource.view()[e.item.index()].value === "0") {

                        this.value("");

                    }

                    else if (current.indexOf("0") !== -1) {

                        current = $.grep(current, function (value) {

                            return value !== "0";

                        });

                        this.value(current);

                    }

                },

                change: function (e) {

                    if (this.value().length === 0)

                        this.value(["0"]);

                }

            }).data("kendoMultiSelect");

        });

        //“Bootstrap v5”设定为jquery.validate.unobtrusive.js中验证样式。

        $.validator.setDefaults({

            errorClass: "",

            validClass: "",

            highlight: function (element, errorClass, validClass) {

                $(element).addClass("is-invalid").removeClass("is-valid");

                $(element.form).find("[data-valmsg-for=" + element.id + "]").addClass("invalid-feedback");

            },

            unhighlight: function (element, errorClass, validClass) {

                $(element).addClass("is-valid").removeClass("is-invalid");

                $(element.form).find("[data-valmsg-for=" + element.id + "]").removeClass("invalid-feedback");

            },

        });

    </script>

}

    对以上功能更为具体实现和注释见230606_024ShopRazor(DataAnnotations自定义手动验证的定义实现)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值