使用 DataAnnotations(数据注解)实现模型的通用数据校验

news2024/12/25 12:20:02

DataAnnotations 实现数据模型的通用校验

  • 参数校验的意义
  • 常用参数的校验
  • .NET 中内置 DataAnnotations 提供的特性校验
    • 关于 DataAnnotations 中的特性介绍
  • 基于 DataAnnotations 的通用模型校验封装
    • 基于 DataAnnotations 的特性校验助手实现步骤
    • 如何使用 DataAnnotations 封装的特性校验助手?
  • 如何实现自定义的验证特性?
    • 自定义校验特性案例
  • 总结

.net 跨平台

参数校验的意义

在实际项目开发中,无论任何方式、任何规模的开发模式,项目中都离不开对接入数据模型参数的合法性校验,目前普片的开发模式基本是前后端分离,当用户在前端页面中输入一些表单数据时,点击提交按钮,触发请求目标服务器的一系列后续操作,在这中间的执行过程中(标准做法推荐)无论是前端代码部分,还是服务端代码部分都应该有针对用户输入数据的合法性校验,典型做法如下:

  • 前端部分:当用户在页面输入表单数据时,前端监听页面表单事件触发相应的数据合法性校验规则,当数据非法时,合理的提示用户数据错误,只有当所有表单数据都校验通过后,才继续提交数据给目标后端对应的接口;
  • 后端部分:当前端数据合法校验通过后,向目标服务器提交表单数据时,服务端接收到相应的提交数据,在入口源头出就应该触发相关的合法性校验规则,当数据都校验通过后,继续执行后续的相关业务逻辑处理,反之则响应相关非法数据的提示信息;

特别说明:在实际的项目中,无论前端部分还是服务端部分,参数的校验都是很有必要性的。无效的参数,可能会导致应用程序的异常和一些不可预知的错误行为。

常用参数的校验

这里例举一些项目中比较常用的参数模型校验项,如下所示:

  • Name:姓名校验,比如需要是纯汉字的姓名;
  • Password:密码强度验证,比如要求用户输入必须包含大小写字母、数字和特殊符号的强密码;
  • QQ号:QQ号码验证,是否是有效合法的QQ号码;
  • China Postal Code:中国邮政编码;
  • IP Address:IPV4 或者 IPV6 地址验证;
  • Phone:手机号码或者座机号码合法性验证;
  • ID Card:身份证号码验证,比如:15位和18位数身份证号码;
  • Email Address:邮箱地址的合法性校验;
  • String:字符串验证,比如字段是否不为 null、长度是否超限;
  • URL:验证属性是否具有 URL 格式;
  • Number:数值型参数校验,数值范围校验,比如非负数,非负整数,正整数等;
  • File:文件路径及扩展名校验;

对于参数校验,常见的方式有正则匹配校验,通过对目标参数编写合法的正则表达式,实现对参数合法性的校验。

.NET 中内置 DataAnnotations 提供的特性校验

上面我们介绍了一些常用的参数验证项,接下来我们来了解下在 .NET 中内置提供的 DataAnnotations 数据注解,该类提供了一些常用的验证参数特性。

官方解释:

  • 提供用于为 ASP.NET MVCASP.NET 数据控件定义元数据的特性类。
  • 该类位于 System.ComponentModel.DataAnnotations 命名空间。

关于 DataAnnotations 中的特性介绍

让我们可以通过这些特性对 API 请求中的参数进行验证,常用的特性一般有:

  • [ValidateNever]: ValidateNeverAttribute 指示应从验证中排除属性或参数。
  • [CreditCard]:验证属性是否具有信用卡格式。
  • [Compare]:验证模型中的两个属性是否匹配。
  • [EmailAddress]:验证属性是否具有电子邮件格式。
  • [Phone]:验证属性是否具有电话号码格式。
  • [Range]:验证属性值是否位于指定范围内。
  • [RegularExpression]:验证属性值是否与指定的正则表达式匹配。
  • [Required]:验证字段是否不为 null。
  • [StringLength]:验证字符串属性值是否不超过指定的长度限制。
  • [Url]:验证属性是否具有 URL 格式。

关于该类更多详细信息请查看,https://learn.microsoft.com/zh-cn/dotnet/api/system.componentmodel.dataannotations?view=net-7.0

基于 DataAnnotations 的通用模型校验封装

此处主要是使用了 Validator.TryValidateObject() 方法:

Validator.TryValidateObject(object instance, ValidationContext validationContext, ICollection<ValidationResult>? validationResults, bool validateAllProperties);

Validator 类提供如下校验方法:

Validator

基于 DataAnnotations 的特性校验助手实现步骤

  • 错误成员对象类 ErrorMember
namespace Jeff.Common.Validatetion;

/// <summary>
/// 错误成员对象
/// </summary>
public class ErrorMember
{
    /// <summary>
    /// 错误信息
    /// </summary>
    public string? ErrorMessage { get; set; }
    /// <summary>
    /// 错误成员名称
    /// </summary>
    public string? ErrorMemberName { get; set; }
}
  • 验证结果类 ValidResult
namespace Jeff.Common.Validatetion;

/// <summary>
/// 验证结果类
/// </summary>
public class ValidResult
{
    public ValidResult()
    {
        ErrorMembers = new List<ErrorMember>();
    }
    /// <summary>
    /// 错误成员列表
    /// </summary>
    public List<ErrorMember> ErrorMembers { get; set; }
    /// <summary>
    /// 验证结果
    /// </summary>
    public bool IsVaild { get; set; }
}
  • 定义操作正则表达式的公共类 RegexHelper
using System;
using System.Net;
using System.Text.RegularExpressions;

namespace Jeff.Common.Validatetion;

/// <summary>
/// 操作正则表达式的公共类
/// Regex 用法参考:https://learn.microsoft.com/zh-cn/dotnet/api/system.text.regularexpressions.regex.-ctor?redirectedfrom=MSDN&view=net-7.0
/// </summary>   
public class RegexHelper
{
    #region 常用正则验证模式字符串
    public enum ValidateType
    {
        Email,                 // 邮箱
        TelePhoneNumber,       // 固定电话(座机)
        MobilePhoneNumber,     // 移动电话
        Age,                   // 年龄(1-120 之间有效)
        Birthday,              // 出生日期
        Timespan,              // 时间戳
        IdentityCardNumber,    // 身份证
        IpV4,                  // IPv4 地址
        IpV6,                  // IPV6 地址
        Domain,                // 域名
        English,               // 英文字母
        Chinese,               // 汉字
        MacAddress,            // MAC 地址
        Url,                   // URL 
    }

    private static readonly Dictionary<ValidateType, string> keyValuePairs = new Dictionary<ValidateType, string>
    {
       { ValidateType.Email, _Email },
       { ValidateType.TelePhoneNumber,_TelephoneNumber },  
       { ValidateType.MobilePhoneNumber,_MobilePhoneNumber }, 
       { ValidateType.Age,_Age }, 
       { ValidateType.Birthday,_Birthday }, 
       { ValidateType.Timespan,_Timespan }, 
       { ValidateType.IdentityCardNumber,_IdentityCardNumber }, 
       { ValidateType.IpV4,_IpV4 }, 
       { ValidateType.IpV6,_IpV6 }, 
       { ValidateType.Domain,_Domain }, 
       { ValidateType.English,_English }, 
       { ValidateType.Chinese,_Chinese }, 
       { ValidateType.MacAddress,_MacAddress }, 
       { ValidateType.Url,_Url }, 
    };

    public const string _Email = @"^(\w)+(\.\w)*@(\w)+((\.\w+)+)$"; // ^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$ , [A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}
    public const string _TelephoneNumber = @"(d+-)?(d{4}-?d{7}|d{3}-?d{8}|^d{7,8})(-d+)?"; //座机号码(中国大陆)
    public const string _MobilePhoneNumber = @"^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$"; //移动电话
    public const string _Age = @"^(?:[1-9][0-9]?|1[01][0-9]|120)$"; // 年龄 1-120 之间有效
    public const string _Birthday = @"^((?:19[2-9]\d{1})|(?:20(?:(?:0[0-9])|(?:1[0-8]))))((?:0?[1-9])|(?:1[0-2]))((?:0?[1-9])|(?:[1-2][0-9])|30|31)$";
    public const string _Timespan = @"^15|16|17\d{11}$"; // 目前时间戳是15开头,以后16、17等开头,长度 10 位是秒级时间戳的正则,13 位时间戳是到毫秒级的。
    public const string _IdentityCardNumber = @"^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$|^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$";
    public const string _IpV4 = @"^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$";
    public const string _IpV6 = @"^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$";
    public const string _Domain = @"^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?$";
    public const string _English = @"^[A-Za-z]+$";
    public const string _Chinese = @"^[\u4e00-\u9fa5]{0,}$";
    public const string _MacAddress = @"^([0-9A-F]{2})(-[0-9A-F]{2}){5}$";
    public const string _Url = @"^[a-zA-z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\S*)?$";
    #endregion

    /// <summary>
    /// 获取验证模式字符串
    /// </summary>
    /// <param name="validateType"></param>
    /// <returns></returns>
    public static (bool hasPattern, string pattern) GetValidatePattern(ValidateType validateType) 
    {
        bool hasPattern = keyValuePairs.TryGetValue(validateType, out string? pattern);
        return (hasPattern, pattern ?? string.Empty);
    }

    #region 验证输入字符串是否与模式字符串匹配,https://www.cnblogs.com/mynameltg/p/4043600.html
    /// <summary>
    /// 验证输入字符串是否与模式字符串匹配
    /// </summary>
    /// <param name="input">输入的字符串</param>
    /// <param name="validateType">模式字符串类型</param>
    /// <param name="matchTimeout">超时间隔</param>
    /// <param name="options">筛选条件</param>
    /// <returns></returns>
    public static (bool isMatch, string info) IsMatch(string input, ValidateType validateType, TimeSpan matchTimeout, RegexOptions options = RegexOptions.None)
    {
        var (hasPattern, pattern) = GetValidatePattern(validateType);
        if (hasPattern && !string.IsNullOrWhiteSpace(pattern))
        {
            bool isMatch = IsMatch(input, pattern, matchTimeout, options);
            if (isMatch) return (true, "Format validation passed."); // 格式验证通过。
            else return (false, "Format validation failed."); // 格式验证未通过。
        }

        return (false, "Unknown ValidatePattern."); // 未知验证模式
    }

    /// <summary>
    /// 验证输入字符串是否与模式字符串匹配,匹配返回true
    /// </summary>
    /// <param name="input">输入字符串</param>
    /// <param name="pattern">模式字符串</param>    
    /// <returns></returns>
    public static bool IsMatch(string input, string pattern)
    {
        return IsMatch(input, pattern, TimeSpan.Zero, RegexOptions.IgnoreCase);
    }

    /// <summary>
    /// 验证输入字符串是否与模式字符串匹配,匹配返回true
    /// </summary>
    /// <param name="input">输入的字符串</param>
    /// <param name="pattern">模式字符串</param>
    /// <param name="matchTimeout">超时间隔</param>
    /// <param name="options">筛选条件</param>
    /// <returns></returns>
    public static bool IsMatch(string input, string pattern, TimeSpan matchTimeout, RegexOptions options = RegexOptions.None)
    {
        return Regex.IsMatch(input, pattern, options, matchTimeout);
    }
    #endregion
}
  • 定义验证结果统一模型格式类 ResponseInfo(此类通常也是通用的数据响应模型类)
namespace Jeff.Common.Model;

public sealed class ResponseInfo<T> where T : class
{
    /*
     Microsoft.AspNetCore.Http.StatusCodes
     System.Net.HttpStatusCode
     */

    /// <summary>
    /// 响应代码(自定义)
    /// </summary>
    public int Code { get; set; }

    /// <summary>
    /// 接口状态
    /// </summary>
    public bool Success { get; set; }

    #region 此处可以考虑多语言国际化设计(语言提示代号对照表)
    /// <summary>
    /// 语言对照码,参考:https://blog.csdn.net/shenenhua/article/details/79150053
    /// </summary>
    public string Lang { get; set; } = "zh-cn";

    /// <summary>
    /// 提示信息
    /// </summary>
    public string Message { get; set; } = string.Empty;
    #endregion

    /// <summary>
    /// 数据体
    /// </summary>
    public T? Data { get; set; }
}
  • 实现验证助手类 ValidatetionHelper,配合 System.ComponentModel.DataAnnotations 类使用
// 数据注解,https://learn.microsoft.com/zh-cn/dotnet/api/system.componentmodel.dataannotations?view=net-7.0
using System.ComponentModel.DataAnnotations;
using Jeff.Common.Model;

namespace Jeff.Common.Validatetion;

/// <summary>
/// 验证助手类
/// </summary>
public sealed class ValidatetionHelper
{
    /// <summary>
    /// DTO 模型校验
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static ValidResult IsValid(object value)
    {
        var result = new ValidResult();
        try
        {
            var validationContext = new ValidationContext(value);
            var results = new List<ValidationResult>();
            bool isValid = Validator.TryValidateObject(value, validationContext, results, true);
            result.IsVaild = isValid;

            if (!isValid)
            {
                foreach (ValidationResult? item in results)
                {
                    result.ErrorMembers.Add(new ErrorMember()
                    {
                        ErrorMessage = item.ErrorMessage,
                        ErrorMemberName = item.MemberNames.FirstOrDefault()
                    });
                }
            }
        }
        catch (ValidationException ex)
        {
            result.IsVaild = false;
            result.ErrorMembers = new List<ErrorMember>
            {
                new ErrorMember()
                {
                    ErrorMessage = ex.Message,
                    ErrorMemberName = "Internal error"
                }
            };
        }

        return result;
    }

    /// <summary>
    /// DTO 模型校验统一响应信息
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="model"></param>
    /// <returns></returns>
    public static ResponseInfo<ValidResult> GetValidInfo<T>(T model) where T : class
    {
        var result = new ResponseInfo<ValidResult>();
        
        var validResult = IsValid(model);
        if (!validResult.IsVaild)
        {
            result.Code = 420;
            result.Message = "DTO 模型参数值异常";
            result.Success = false;
            result.Data = validResult;
        }
        else
        {
            result.Code = 200;
            result.Success = true;
            result.Message = "DTO 模型参数值合法";
        }

        return result;
    }
}

如何使用 DataAnnotations 封装的特性校验助手?

  • 首先定义一个数据模型类(DTO)
using System.ComponentModel.DataAnnotations;
using Jeff.Mes.Common.Validatetion;

namespace Jeff.Comm.Test;

public class Person
{
    [Display(Name = "姓名"), Required(ErrorMessage = "{0}必须填写")]
    public string Name { get; set; }

    [Display(Name = "邮箱")]
    [Required(ErrorMessage = "{0}必须填写")]
    [RegularExpression(RegexHelper._Email, ErrorMessage = "RegularExpression: {0}格式非法")]
    [EmailAddress(ErrorMessage = "EmailAddress: {0}格式非法")]
    public string Email { get; set; }

    [Display(Name = "Age年龄")]
    [Required(ErrorMessage = "{0}必须填写")]
    [Range(1, 120, ErrorMessage = "超出范围")]
    [RegularExpression(RegexHelper._Age, ErrorMessage = "{0}超出合理范围")]
    public int Age { get; set; }

    [Display(Name = "Birthday出生日期")]
    [Required(ErrorMessage = "{0}必须填写")]
    [RegularExpression(RegexHelper._Timespan, ErrorMessage = "{0}超出合理范围")]
    public TimeSpan Birthday { get; set; }

    [Display(Name = "Address住址")]
    [Required(ErrorMessage = "{0}必须填写")]
    [StringLength(200, MinimumLength = 10, ErrorMessage = "{0}输入长度不正确")]
    public string Address { get; set; }

    [Display(Name = "Mobile手机号码")]
    [Required(ErrorMessage = "{0}必须填写")]
    [RegularExpression(RegexHelper._MobilePhoneNumber, ErrorMessage = "{0}格式非法")]
    public string Mobile { get; set; }

    [Display(Name = "Salary薪水")]
    [Required(ErrorMessage = "{0}必须填写")]
    [Range(typeof(decimal), "1000.00", "3000.99")]
    public decimal Salary { get; set; }

    [Display(Name = "MyUrl连接")]
    [Required(ErrorMessage = "{0}必须填写")]
    [Url(ErrorMessage = "Url:{0}格式非法")]
    [RegularExpression(RegexHelper._Url, ErrorMessage = "RegularExpression:{0}格式非法")]
    public string MyUrl { get; set; }
}
  • 控制台调用通用校验助手验证方法 ValidatetionHelper.IsValid()ValidatetionHelper.GetValidInfo()
// 通用模型数据验证测试
static void ValidatetionTest() 
{
    var p = new Person
    {
        Name = "",
        Age = -10,
        Email = "www.baidu.com",
        MobilePhoneNumber = "12345",
        Salary = 4000,
        MyUrl = "aaa"
    };

    // 调用通用模型校验
    var result = ValidatetionHelper.IsValid(p);
    if (!result.IsVaild)
    {
        foreach (ErrorMember errorMember in result.ErrorMembers)
        {
            // 控制台打印字段验证信息
            Console.WriteLine($"{errorMember.ErrorMemberName}{errorMember.ErrorMessage}");
        }
    }
    Console.WriteLine();

    // 调用通用模型校验,返回统一数据格式
    var validInfo = ValidatetionHelper.GetValidInfo(p);
    var options = new JsonSerializerOptions
    {
        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, // 设置中文编码乱码
        WriteIndented = false
    };
    string jsonStr = JsonSerializer.Serialize(validInfo, options);
    Console.WriteLine($"校验结果返回统一数据格式:{jsonStr}");
}

在控制台Program.Main 方法中调用 ValidatetionTest() 方法:

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");
        {
            #region 数据注解(DataAnnotations)模型验证
            ValidatetionTest(); 
            #endregion
        }
        Console.ReadKey();
    }

启动控制台,输出如下信息:

ValidatetionHelper.IsValid

如何实现自定义的验证特性?

当我们碰到这些参数需要验证的时候,而上面内置类提供的特性不能满足需求时,此时我们可以实现自定义的验证特性来满足校验需求,按照微软给出的编码规则,我们只需继承 ValidationAttribute 类,并重写 IsValid() 方法即可。

自定义校验特性案例

比如实现一个密码强度的验证,实现步骤如下:

  • 定义密码强度规则,只包含英文字母、数字和特殊字符的组合,并且组合长度至少 8 位数
/// <summary>
/// 只包含英文字母、数字和特殊字符的组合
/// </summary>
/// <returns></returns>
public static bool IsCombinationOfEnglishNumberSymbol(string input, int? minLength = null, int? maxLength = null)
{
    var pattern = @"(?=.*\d)(?=.*[a-zA-Z])(?=.*[^a-zA-Z\d]).";
    if (minLength is null && maxLength is null)
        pattern = $@"^{pattern}+$";
    else if (minLength is not null && maxLength is null)
        pattern = $@"^{pattern}{{{minLength},}}$";
    else if (minLength is null && maxLength is not null)
        pattern = $@"^{pattern}{{1,{maxLength}}}$";
    else
        pattern = $@"^{pattern}{{{minLength},{maxLength}}}$";
    return Regex.IsMatch(input, pattern);
}
  • 实现自定义特性 EnglishNumberSymbolCombinationAttribute,继承自 ValidationAttribute
using System.ComponentModel.DataAnnotations;

namespace Jeff.Common.Validatetion.CustomAttributes;

/// <summary>
/// 是否是英文字母、数字和特殊字符的组合
/// </summary>
public class EnglishNumberSymbolCombinationAttribute : ValidationAttribute
{
    /// <summary>
    /// 默认的错误提示信息
    /// </summary>
    private const string error = "无效的英文字母、数字和特殊字符的组合";

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value is null) return new ValidationResult("参数值为 null");

        //if (value is null)
        //{
        //    throw new ArgumentNullException(nameof(attribute));
        //}

        //这里是验证的参数的逻辑 value是需要验证的值  而validationContext中包含了验证相关的上下文信息 这里我是有一个自己封装的验证格式的FormatValidation类
        if (FormatValidation.IsCombinationOfEnglishNumberSymbol(value as string, 8))
            //验证成功返回 success
            return ValidationResult.Success;
        //不成功 提示验证错误的信息
        else return new ValidationResult(ErrorMessage ?? error);
    }
}

以上就实现了一个自定义规则的自定义验证特性,使用方式很简单,可以把它附属在我们请求的参数上或者 DTO 里的属性,也可以是 Action 上的形参,如下所示:

public class CreateDTO
{
   [Required]
   public string StoreName { get; init; }
   [Required]
   // 附属在 DTO 里的属性
   [EnglishNumberSymbolCombination(ErrorMessage = "UserId 必须是英文字母、数字和特殊符号的组合")]
   public string UserId { get; init; }
}
...
// 附属在 Action 上的形参
[HttpGet]
public async ValueTask<ActionResult> Delete([EnglishNumberSymbolCombination]string userId, string storeName)

更多自定义参数校验特性,可参照上面案例的实现思路,感兴趣的小伙伴可自行扩展实现哟。

总结

对于模型参数的校验,在实际项目系统中是非常有必要性的(通常在数据源头尽早验证),利用 .NET 内置的 DataAnnotations(数据注解)提供的特性校验,可以很方便的实现通用的模型校验助手,关于其他特性的用法,请自行参考微软官方文档,这里注意下RegularExpressionAttribute(指定 ASP.NET 动态数据中的数据字段值必须与指定的正则表达式匹配),该特性可以方便的接入正则匹配验证,当遇到复杂的参数校验时,可以快速方便的扩展自定义校验特性,从此告别传统编码中各种 if(xxx != yyyy) 判断的验证,让整体代码编写更佳简练干净。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/115003.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

某农业学校 算法设计与分析-第五次实验-回溯算法

1. 罗密欧与朱丽叶的迷宫问题 问题描述 罗密欧与朱丽叶的迷宫。罗密欧与朱丽叶身处一个mn的迷宫中&#xff0c;如图所示。每一个方格表示迷宫中的一个房间。这mn个房间中有一些房间是封闭的&#xff0c;不允许任何人进入。在迷宫中任何位置均可沿8 个方向进入未封闭的房间。罗…

第二章:关系数据库

一、关系数据库结构及形式化定义 1、【单选题】 下图中&#xff0c;关系D1、D2、D3笛卡尔积的目和基数分别为 正确答案&#xff1a; B 2、【多选题】下图中能够作为候选码的属性组为 正确答案&#xff1a; ABD 3、【多选题】关于关系数据库&#xff0c;说法正确的是 正确答…

二、栈和队列

二、栈和队列 栈——后进先出 应用&#xff1a;数制转换、括号匹配、行编辑程序、迷宫求解、表达式求值、八皇后问题、函数调用、递归调用的实现 队列——先进先出 应用&#xff1a;脱机打印输出 多用户系统用户排队分时循环使用CPU和主存 按用户优先级排队&#xff0c;每…

编译gtest报错‘is_trivially_copy_constructible’ is not a member of ‘std’

编译gtest报错‘is_trivially_copy_constructible’ is not a member of ‘std’一、问题描述二、原因分析三、升级gcc版本四、验证一、问题描述 在一个新的Redhat7.6 linux虚拟机上&#xff0c;将gtest clone下来之后编译&#xff0c;一堆报错&#xff1a; /opt/googletest/…

多线程问题(二)(安全问题)

目录 一、多线程不安全引例 二、线程不安全的原因 1、线程是抢占式执行 2、多线程共享同一变量 3、对变量的操作不是原子性 4、内存可见性 5、指令重排序 三、线程不安全问题的解决方案 1、使用synchronized关键字进行加锁 a、 synchronized修饰普通方法 b、sy…

Maleimide-PEG-Biotin,Biotin-PEG-MAL,生物素PEG马来酰亚胺用于生物分子检测

化学试剂生物素聚乙二醇马来酰亚胺&#xff0c;其英文名为Maleimide-PEG-Biotin&#xff0c;Biotin-PEG-MAL&#xff0c;它所属分类为Biotin PEG Multi-arm PEGs。 该试剂质量控制为95%&#xff0c;试剂的储存条件为&#xff1a; -20℃长期保存&#xff0c;避光&#xff0c;干…

数据结构---图

&#xff08;一&#xff09; 相关知识点 图&#xff08;graph&#xff09;&#xff1a;图是由顶点的有穷非空集合和顶点之间边的集合组成&#xff0c;通常表示为&#xff1a;G(V,E)&#xff0c;其中&#xff0c;G表示一个图&#xff0c;V是图G中的顶点的集合&#xff0c;E是图G…

SpringBoot系列之自动装配原理详解

文章目录前言一、SpringBoot自动配置-Condition-11、观察spring自动创建bean过程2、创建自定义bean对象3、根据条件创建自定义bean二、 SpringBoot自动配置-Condition-2三、SpringBoot自动配置-切换内置web服务器1、查看继承关系图2、shiftdelete 排除Tomcat四、SpringBoot自动…

Win10启动Pycharm报错

Win10启动Pycharm报错报错信息解决方法报错信息 Internal error. Please report to http://jb.gg/ide/critical-startup-errors java.net.BindException: Address already in use: bind at java.base/sun.nio.ch.Net.bind0(Native Method) at java.base/sun.nio.ch.Net.bind(U…

如何在3个月内写出博士论文

在阅读本文之前&#xff0c;请注意&#xff1a;我花了三年半的时间进行全职研究&#xff0c;为我的博士论文收集数据&#xff1b;这三个月只涉及写作&#xff0c;我在最后很快就完成了。我并不是说每个人都能写得那么快&#xff0c;如果你没有做过研究&#xff0c;那是不可能的…

全国各省368个地级市河流密度数据(工具变量)

数据来源&#xff1a;国家基础地理信息中心 时间跨度&#xff1a;-- 区域范围&#xff1a;全国各省市 指标说明&#xff1a; 根据河流矢量和中国城市行政边界矢量地理信息&#xff0c;计算每个城市河流的总长度&#xff1b;根据各城市的行政区划面积&#xff0c;计算中国各城…

第三章:关系数据库标准语言SQL

一、sql概述和数据定义 1、【单选题】 create user A identified by B default tablespace C temporary tablespace D&#xff1b; 上述oracle数据库查询语句中A、B、C、D分别代表&#xff1a; 正确答案&#xff1a; A 2、【单选题】下表为患者缴费记录&#xff0c;现需…

2023跨年烟花3D最炫烟花,html最酷炫动态烟花源码分享,点击即可直接运行

&#x1f4cb; 前言 &#x1f5b1; 博客主页&#xff1a;在下马农的碎碎念✍ 本文由在下马农原创&#xff0c;首发于CSDN&#x1f4c6; 首发时间&#xff1a;2022/12/25&#x1f4c5; 最近更新时间&#xff1a;2022/12/25&#x1f935; 此马非凡马&#xff0c;房星本是星。向前…

lambda表达式,函数式接口,链式编程,Stream流式计算

新时代的程序员&#xff1a;lambda表达式&#xff0c;函数式接口&#xff0c;链式编程&#xff0c;Stream流式计算 函数式接口 函数式接口&#xff1a;只有一个方法的接口(简化编程模型&#xff0c;在新版本框架底层中大量应用&#xff01;) 只要是 函数型接口 就可以使用lambd…

iOS 16.2 在 SwiftUI 子视图中无法关闭弹出的(sheet)导航视图(NavigationView)之解决

问题现象 iOS 16.2 中,若在 SwiftUI 4.0 里弹出(sheet)一个导航视图 A,则不能在 A 的子视图中将 A 关闭(dismiss): 如上图所示:上面的按钮直接放在导航视图 A 中,点击它可以直接关闭 A;而下面的按钮放在一个子视图中,点击它想要关闭 A 却没有任何反应。 那么,该如…

可转债网格交易策略回测

什么是网格交易策略&#xff1a;基于股票波动高抛低吸策略&#xff0c;自动化反复买卖赚取差价。投资者借助条件单&#xff0c;把资金分成多份&#xff0c;从基准价开始&#xff0c;每跌x%就自动买入一份&#xff0c;每涨y%就自动卖掉一份。股价越波动高抛低吸的机会越多 什么…

Java项目:springboot基于java+mysql+springboot的社区养老医疗综合服务平台

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目分为管理员、医生、病人三种角色&#xff0c; 管理员的功能包含如下&#xff1a; 个人信息&#xff1a;个人资料、修改密码 系统管理&…

Python入门学习之字符串与比较运算符

Python字符串 字符串或串(String)是由数字、字母、下划线组成的一串字符。 一般记为 : 1 s"a1a2an"(n>0) 它是编程语言中表示文本的数据类型。 python的字串列表有2种取值顺序: 从左到右索引默认0开始的&#xff0c;最大范围是字符串长度少1从右到左索引默认-1开…

【C++】各种排序涉及到的选择小题合集(每日小细节009)

昨天更新完各种排序之后今天来检验一下是否真的全部掌握了呢&#xff1f; 今天的合集里面包括各种排序的综合选择题和一些解题技巧 花一两分钟看一下真的很有帮助哦 &#xff08;最好能先认真复习一下各种排序&#xff09; 1. 冒泡排序就是相邻元素的两两比较所以依次写出来就…

Java项目:Springboot体育器材管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 体育器材管理系统主要包含以下功能&#xff1a; 登录注册&#xff1b; 体育器材管理&#xff1a;显示器材表、显示价目表、显示供应商表&#x…