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

dd09848975753cc1dfa9b68761d6585a.png

.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]**:指示应从验证中排除属性或参数。

  • **[CreditCard]**:验证属性是否具有信用卡格式。

  • **[Compare]**:验证模型中的两个属性是否匹配。

  • **[EmailAddress]**:验证属性是否具有电子邮件格式。

  • **[Phone]**:验证属性是否具有电话号码格式。

  • **[Range]**:验证属性值是否位于指定范围内。

  • **[RegularExpression]**:验证属性值是否与指定的正则表达式匹配。

  • **[Required]**:验证字段是否不为 null。

  • **[StringLength]**:验证字符串属性值是否不超过指定的长度限制。

  • **[Url]**:验证属性是否具有 URL 格式。

其中 RegularExpression 特性,基于正则表达式可以扩展实现很多常用的验证类型,下面的( 基于 DataAnnotations 的通用模型校验封装 )环节举例说明。

关于该类更多详细信息请查看,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 类提供如下校验方法:

33f5227fcc18df1e13328cebe66144af.png
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(基于 RegularExpression 特性扩展)

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{8,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 验证输入字符串是否与模式字符串匹配/// <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.StatusCodesSystem.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),添加校验特性 ValidationAttribute

using System.ComponentModel.DataAnnotations;
using Jeff.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, DataAnnotations!");{#region 数据注解(DataAnnotations)模型验证ValidatetionTest(); #endregion}Console.ReadKey();}

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

b49133362f905871efe19926b24c67c1.png

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}}}$";elsepattern = $@"^{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))//验证成功返回 successreturn 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)

该自定义验证特性还可以结合 DataAnnotations 内置的 [Compare] 特性,可以实现账号注册的密码确认验证(输入密码和确认密码是否一致性)。关于更多自定义参数校验特性,感兴趣的小伙伴可参照上面案例的实现思路,自行扩展实现哟。

总结

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

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

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

相关文章

网线的做法 及 POE的介绍

网线的做法 以太网线采用差分方式传输。所谓差分方式传输&#xff0c;就是发送端在两条信号线上传输幅值相等相位相反的电信号&#xff0c;接收端对接受的两条线信号作减法运算&#xff0c;这样获得幅值翻倍的信号。其抗干扰的原理是&#xff1a;假如两条信号线都受到了同样&am…

unity 使用tile_如何使用Tile从网上查找电话

unity 使用tileTile is a fantastic little gadget that can help you find your lost keys or wallet. However, it can also locate and ring your phone, even if you never buy a single physical Tile. Here’s how to find your lost phone using the Tile app on the we…

《ASP.NET Core 6框架揭秘实例》演示[35]:利用Session保留语境

客户端和服务器基于HTTP的消息交换就好比两个完全没有记忆能力的人在交流&#xff0c;每次单一的HTTP事务体现为一次“一问一答”的对话。单一的对话毫无意义&#xff0c;在在同一语境下针对某个主题进行的多次对话才会有结果。会话的目的就是在同一个客户端和服务器之间建立两…

Vincross孙天齐:人机界面的突破将引发科技革命

8月23—27日&#xff0c;世界机器人大会在北京举办&#xff0c;全球各国机器人领域的优秀企业悉数亮相&#xff0c;五花八门的机器人及产业链上下游最新技术均能在这次盛会上找到踪迹&#xff0c;整个会场充满了未来感与时代发展的气息。 大会中智慧城市服务机器人技术与应用专…

如何在Windows上使用64位Web浏览器

Google and Mozilla now offer 64-bit versions of Chrome and Firefox for Windows. Here’s how to find out what version you’re running and how to upgrade. Google和Mozilla现在提供适用于Windows的64位版本的Chrome和Firefox。 这是找出正在运行的版本以及如何升级的方…

立下“去O”Flag的AWS,悄悄修炼了哪些内功?

AWS re:Invent 2018大会上&#xff0c;AWS首席执行执行官Andy Jassy 表示到 2019 年底&#xff0c;亚马逊将全面放弃使用 Oracle 数据库&#xff0c;97&#xff05;的“关键任务数据库”将运行在亚马逊自己的数据库服务上。 如今&#xff0c;2019年已经过去了四分之一&#xff…

刘强东痛批京东高管,拿PPT骗他!网友怒了:爱用 PPT 忽悠的人,他们都遭人痛恨...

这是头哥侃码的第272篇原创因为被新冠感染&#xff0c;所以最近两周都在休养。前几天&#xff0c;我无意中看到一则有关刘强东的新闻&#xff0c;大致是他在京东内部管理培训会上痛批部分高管&#xff0c;称 “拿PPT和假大空词汇忽悠自己的人就是骗子”&#xff0c;表示部分高管…

关于file的部分简单命令

1.关于file的简单命令 2.创建/删除 文件/目录 ## -f和-r可以连用&#xff0c;表示强制删除 3.文件/目录的复制 ##复制是一个新建的过程&#xff0c;在保持原有不变的基础上重新再建立一个 4.文件/目录的移动 ##移动是一个重命名的过程&#xff0c;但不改变其中的内容 本文转自…

字节与浮点型转换软件_如何与另一个防病毒软件一起运行恶意软件字节

字节与浮点型转换软件Malwarebytes Anti-Malware is a great security tool that’s particularly effective against “potentially unwanted programs (PUPs)” and other nasty software traditional antivirus programs don’t deal with. But it’s intended to be used a…

火狐浏览器书签(收藏夹)全部消失,历史记录也消失,如何恢复

今天关闭再打开火狐浏览器瞬间懵逼&#xff0c;浏览器所有的记录都没了&#xff0c;映入眼帘的的火狐新手指导页&#xff0c;而且主页导航变成了hao123&#xff0c;我估计是外部程序篡改了浏览器配置&#xff0c;或者其他异常导致浏览器重置。书签、历史记录对开发人员的重要性…

apple tv 开发_如何防止Apple TV进入睡眠状态

apple tv 开发Your Apple TV, by default, goes to sleep fairly quickly when not in use. That’s great for power saving but not so great if you like to keep it on. Let’s take a look at how to extend how long it stays awake or disable sleep mode altogether. 默…

MASA MAUI Plugin (七)应用通知角标(小红点)Android+iOS

背景MAUI的出现&#xff0c;赋予了广大Net开发者开发多平台应用的能力&#xff0c;MAUI 是Xamarin.Forms演变而来&#xff0c;但是相比Xamarin性能更好&#xff0c;可扩展性更强&#xff0c;结构更简单。但是MAUI对于平台相关的实现并不完整。所以MASA团队开展了一个实验性项目…

SAP如何查看会计凭证

比如SAP中已经存在着很多会计凭证&#xff0c;你想要进入SAP随便看看会计凭证的列表&#xff0c;怎么操作呢&#xff1f;事务码 IDCNDOC运行结果看到了凭证们&#xff0c;和每个凭证的行项目们上图看到的结果比较凌乱实际上我们重新进入IDCNDOC可以通过输入的勾选&#xff0c;选…

C# 温故而知新:Stream篇(五)

MemoryStream 目录&#xff1a; 1 简单介绍一下MemoryStream 2 MemoryStream和FileStream的区别 3 通过部分源码深入了解下MemoryStream 4 分析MemorySteam最常见的OutOfMemory异常 5 MemoryStream 的构造 6 MemoryStream 的属性 7 MemoryStream 的方法 8 MemoryStream 简单示例…

dosbox 自动运行_如何使用DOSBox运行DOS游戏和旧应用

dosbox 自动运行New versions of Windows don’t fully support classic DOS games and other old applications — this is where DOSBox comes in. It provides a full DOS environment that runs ancient DOS apps on modern operating systems. Windows的新版本不完全支持经…

WPF 自定义放大镜控件

控件名&#xff1a;Magnifier作 者&#xff1a;WPFDevelopersOrg - 驚鏵原文链接[1]&#xff1a;https://github.com/WPFDevelopersOrg/WPFDevelopers框架使用.NET40&#xff1b;Visual Studio 2019;实现此功能需要用到 VisualBrush &#xff0c;放大镜展现使用 Canvas ->…

.NET实现之(WebBrowser数据采集—续篇)

我们继续“.NET实现之(WebBrowser数据采集)“系列篇之最后一篇&#xff0c;这篇本人打算主要讲解怎么用WebBrowser控件来实现“虚拟”的交互性程序&#xff1b;比如我们用Winform做为宿主容器&#xff0c;用Asp.net做相关收集程序页面&#xff0c;我们需要通过客户端填写相关数…

ipad和iphone切图_如何在iPhone,iPad和Mac上使消息静音

ipad和iphone切图If you use Messages on your iPhone, iPad, or Mac, then you probably know how quickly you can become overrun with message notifications, especially if you’re part of a group message. Thankfully, there’s an easy way to mute specific message…

Pipy 实现 SOCKS 代理

上篇我们介绍了服务网格 osm-edge 出口网关使用的 HTTP 隧道&#xff0c;其处理方式与另一种代理有点类似&#xff0c;就是今天要介绍的 SOCKS 代理。二者的主要差别简单来说就是前者使用 HTTP CONNECT 告知代理目的地址&#xff0c;而后者则是通过 SOCKS 协议。值得一提的是&a…

python拓展7(Celery消息队列配置定时任务)

介绍 celery 定时器是一个调度器&#xff08;scheduler&#xff09;&#xff1b;它会定时地开启&#xff08;kicks off&#xff09;任务&#xff0c;然后由集群中可用的工人&#xff08;worker&#xff09;来执行。 定时任务记录&#xff08;entries&#xff09;默认 从 beat_s…