最近在做一个EDI项目,主要流程就是客户以HttpPost或Webservice的方式向我们公司下订单,订单内容是以XML格式表示,我这边需要做的操作是:
一: 验证请求是否合法(双方密钥)
二: 验证请求内容是否正确且符合一定的格式要求
三: 对订单进行处理
验证用户的请求是否合法以及对订单的处理就不说了,我今天主要说的是一种优雅、美观、清爽、干净的验证方式
对于XML的内容,我这边的处理方式是将它反序列化成实体对象,毕竟操作一个实体对象比一大堆的XPath强多了。
.net framework自带的XML序列化和反序列化类System.Xml.Serialization.XmlSerializer由于内部实现过于复杂,导致性能不佳 。 我这边自己实现了一个XML反序列化类,性能虽好但比较有针对性,所以今天还是以.net framework自带的XML反序列化类作为示范。
比如说一个XML的内容是这样:
<OrderRequest>
<!-- 订单号 -->
<OrderNo>T-1234567</OrderNo>
<!-- 商品名称 -->
<CommodityName>笔记本电脑</CommodityName>
<!-- 商品数量 -->
<CommodityAmount>1</CommodityAmount>
<!-- 商品重量 单位:KG -->
<CommodityWeight>5.27</CommodityWeight>
<!-- 商品价格 -->
<CommodityValue>13999.00</CommodityValue>
<!-- 希望到达时间 -->
<HopeArriveTime>2010-09-01 00:00:00</HopeArriveTime>
<!-- 结算方式 只能为现结、到付和月结三种 -->
<PayMent>现结</PayMent>
<!-- 备注 -->
<Remark>小心轻放</Remark>
</OrderRequest>
当然,正常的订单不会只有这么点内容,下面我们要为它设计一个实体类:
/// 订单实体类
/// </summary>
public class OrderRequest
{
/// <summary>
/// 订单号
/// </summary>
public string OrderNo { get; set; }
/// <summary>
/// 商品名称
/// </summary>
public string CommodityName { get; set; }
/// <summary>
/// 商品数量
/// </summary>
public string CommodityAmount { get; set; }
/// <summary>
/// 商品重量
/// </summary>
public string CommodityWeight { get; set; }
/// <summary>
/// 商品价格
/// </summary>
public string CommodityValue { get; set; }
/// <summary>
/// 希望到货时间
/// </summary>
public string HopeArriveTime { get; set; }
/// <summary>
/// 结算方式
/// </summary>
public string PayMent { get; set;}
/// <summary>
/// 备注
/// </summary>
public string Remark { get; set; }
}
可能有的朋友会说,你这也忒不专业了,所有类型都是string。别着急,我想说的重点是验证方式,类型转换之类的先放一边吧。
然后利用微软自带的XML反序列化类将XML反序列成对象,下面是反序列化的方法:
using System.IO;
using System.Xml;
using System.Xml.Serialization;
/// <summary>
/// 反序列化类
/// </summary>
public class XmlAntiSerialization
{
/// <summary>
/// 反序列化
/// </summary>
/// <param name="type">实体对象类型</param>
/// <param name="xml">XML字符串</param>
/// <returns></returns>
public static object Deserialize(Type type, string xml)
{
try
{
using (StringReader sr = new StringReader(xml))
{
XmlSerializer xmldes = new XmlSerializer(type);
return xmldes.Deserialize(sr);
}
}
catch(Exception ex)
{
throw ex;
}
}
}
只需如此调用即可将XML内容反序列化:
传统的验证方式大概是这样的:
if (string.IsNullOrEmpty(orderRequest.OrderNo))
return "订单号不能为空";
if(string.IsNullOrEmpty(orderRequest.CommodityAmount) || !System.Text.RegularExpressions.Regex.IsMatch(orderRequest.CommodityAmount,@"^\d+$"))
return "商品数量不能为空且只能为数字";
if (string.IsNullOrEmpty(orderRequest.PayMent) || (orderRequest.PayMent != "现结" && orderRequest.PayMent != "到付" && orderRequest.PayMent != "月结"))
return "结算方式不能为空,且必须为现结、到付或月结的一种";
如此这般一大堆,想保证页面的清爽是不大可能了,按老赵的话说就是一股语法噪音。但C sharp 这把锋利的刃还给我们提供了更趁手的武器:特性(Attribute)
好吧,让我们先定义一个验证方式的枚举:
/// 验证类型
/// </summary>
[Flags]
public enum ValidateType
{
/// <summary>
/// 字段或属性是否为空字串
/// </summary>
IsEmpty = 0x0001,
/// <summary>
/// 字段或属性的最小长度
/// </summary>
MinLength = 0x0002,
/// <summary>
/// 字段或属性的最大长度
/// </summary>
MaxLength = 0x0004,
/// <summary>
/// 字段或属性的值是否为数值型
/// </summary>
IsNumber = 0x0008,
/// <summary>
/// 字段或属性的值是否为时间类型
/// </summary>
IsDateTime = 0x0010,
/// <summary>
/// 字段或属性的值是否为正确的浮点类型
/// </summary>
IsDecimal = 0x0020,
/// <summary>
/// 字段或属性的值是否包含在指定的数据源数组中
/// </summary>
IsInCustomArray = 0x0040,
/// <summary>
/// 字段或属性的值是否为固定电话号码格式
/// </summary>
IsTelphone = 0x0080,
/// <summary>
/// 字段或属性的值是否为手机号码格式
/// </summary>
IsMobile = 0x0100
}
再实现一个自定义的特性类:
/// 为元素添加验证信息的特性类
/// </summary>
[AttributeUsage(AttributeTargets.All)]
public class ValidateAttribute : Attribute
{
/// <summary>
/// 验证类型
/// </summary>
private ValidateType _validateType;
/// <summary>
/// 最小长度
/// </summary>
private int _minLength;
/// <summary>
/// 最大长度
/// </summary>
private int _maxLength;
/// <summary>
/// 自定义数据源
/// </summary>
private string[] _customArray;
/// <summary>
/// 验证类型
/// </summary>
public ValidateType ValidateType
{
get { return this._validateType; }
}
/// <summary>
/// 最小长度
/// </summary>
public int MinLength
{
get { return this._minLength; }
set { this._minLength = value; }
}
/// <summary>
/// 最大长度
/// </summary>
public int MaxLength
{
get { return this._maxLength; }
set { this._maxLength = value; }
}
/// <summary>
/// 自定义数据源
/// </summary>
public string[] CustomArray
{
get { return this._customArray; }
set { this._customArray = value; }
}
/// <summary>
/// 指定采取何种验证方式来验证元素的有效性
/// </summary>
/// <param name="validateType"></param>
public ValidateAttribute(ValidateType validateType)
{
this._validateType = validateType;
}
}
下面我们就可以在实体类的属性上增加特性验证:
{
/// <summary>
/// 订单号
/// </summary>
[Validate(ValidateType.IsEmpty)]
public string OrderNo { get; set; }
/// <summary>
/// 商品名称
/// </summary>
[Validate(ValidateType.IsEmpty|ValidateType.MaxLength,MaxLength = 50)]
public string CommodityName { get; set; }
/// <summary>
/// 商品数量
/// </summary>
[Validate(ValidateType.IsEmpty|ValidateType.IsNumber)]
public string CommodityAmount { get; set; }
/// <summary>
/// 商品重量
/// </summary>
[Validate(ValidateType.IsEmpty | ValidateType.IsDecimal)]
public string CommodityWeight { get; set; }
/// <summary>
/// 商品价格
/// </summary>
[Validate(ValidateType.IsEmpty | ValidateType.IsDecimal)]
public string CommodityValue { get; set; }
/// <summary>
/// 希望到货时间
/// </summary>
[Validate(ValidateType.IsEmpty | ValidateType.IsDateTime)]
public string HopeArriveTime { get; set; }
/// <summary>
/// 结算方式
/// </summary>
[Validate(ValidateType.IsEmpty | ValidateType.IsInCustomArray,CustomArray = new string[]{"现结","到付","月结"})]
public string PayMent { get; set;}
/// <summary>
/// 备注
/// </summary>
[Validate(ValidateType.MaxLength,MaxLength = 256)]
public string Remark { get; set; }
}
由于我们的枚举实用了位标记(FlagsAttribute),所以我们可以对某个元素使用多种验证方式。下面就是验证的实现:
/// 验证实体对象的所有带验证特性的元素 并返回验证结果 如果返回结果为String.Empty 则说明元素符合验证要求
/// </summary>
/// <param name="entityObject">实体对象</param>
/// <returns></returns>
public static string GetValidateResult(object entityObject)
{
if (entityObject == null)
throw new ArgumentNullException("entityObject");
Type type = entityObject.GetType();
PropertyInfo[] properties = type.GetProperties();
string validateResult = string.Empty;
foreach (PropertyInfo property in properties)
{
//获取验证特性
object[] validateContent = property.GetCustomAttributes(typeof(ValidateAttribute), true);
if (validateContent != null)
{
//获取属性的值
object value = property.GetValue(entityObject, null);
foreach (ValidateAttribute validateAttribute in validateContent)
{
switch (validateAttribute.ValidateType)
{
//验证元素是否为空字串
case ValidateType.IsEmpty:
if (null == value || value.ToString().Length < 1)
validateResult = string.Format("元素 {0} 不能为空", property.Name);
break;
//验证元素的长度是否小于指定最小长度
case ValidateType.MinLength:
if (null == value || value.ToString().Length < 1) break;
if (value.ToString().Length < validateAttribute.MinLength)
validateResult = string.Format("元素 {0} 的长度不能小于 {1}", property.Name, validateAttribute.MinLength);
break;
//验证元素的长度是否大于指定最大长度
case ValidateType.MaxLength:
if (null == value || value.ToString().Length < 1) break;
if (value.ToString().Length > validateAttribute.MaxLength)
validateResult = string.Format("元素 {0} 的长度不能大于{1}", property.Name, validateAttribute.MaxLength);
break;
//验证元素的长度是否符合指定的最大长度和最小长度的范围
case ValidateType.MinLength | ValidateType.MaxLength:
if (null == value || value.ToString().Length < 1) break;
if (value.ToString().Length > validateAttribute.MaxLength || value.ToString().Length < validateAttribute.MinLength)
validateResult = string.Format("元素 {0} 不符合指定的最小长度和最大长度的范围,应该在 {1} 与 {2} 之间", property.Name, validateAttribute.MinLength, validateAttribute.MaxLength);
break;
//验证元素的值是否为值类型
case ValidateType.IsNumber:
if (null == value || value.ToString().Length < 1) break;
if (!System.Text.RegularExpressions.Regex.IsMatch(value.ToString(), @"^\d+$"))
validateResult = string.Format("元素 {0} 的值不是值类型", property.Name);
break;
//验证元素的值是否为正确的时间格式
case ValidateType.IsDateTime:
if (null == value || value.ToString().Length < 1) break;
if (!System.Text.RegularExpressions.Regex.IsMatch(value.ToString(), @"(\d{2,4})[-/]?([0]?[1-9]|[1][12])[-/]?([0][1-9]|[12]\d|[3][01])\s*([01]\d|[2][0-4])?[:]?([012345]?\d)?[:]?([012345]?\d)?"))
validateResult = string.Format("元素 {0} 不是正确的时间格式", property.Name);
break;
//验证元素的值是否为正确的浮点格式
case ValidateType.IsDecimal:
if (null == value || value.ToString().Length < 1) break;
if (!System.Text.RegularExpressions.Regex.IsMatch(value.ToString(), @"^\d+[.]?\d+$"))
validateResult = string.Format("元素 {0} 不是正确的金额格式", property.Name);
break;
//验证元素的值是否在指定的数据源中
case ValidateType.IsInCustomArray:
if (null == value || value.ToString().Length < 1) break;
if (null == validateAttribute.CustomArray || validateAttribute.CustomArray.Length < 1)
validateResult = string.Format("系统内部错误:元素 {0} 指定的数据源为空或没有数据", property.Name);
bool isHas = Array.Exists<string>(validateAttribute.CustomArray, delegate(string str)
{
return str == value.ToString();
}
);
if (!isHas)
validateResult = string.Format("元素 {0} 的值设定不正确 , 应该为 {1} 中的一种", property.Name, string.Join(",", validateAttribute.CustomArray));
break;
//验证元素的值是否为固定电话号码格式
case ValidateType.IsTelphone:
if (null == value || value.ToString().Length < 1) break;
if (!System.Text.RegularExpressions.Regex.IsMatch(value.ToString(), @"^(\d{3,4}-)?\d{6,8}$"))
validateResult = string.Format("元素 {0} 不是正确的固定电话号码格式", property.Name);
break;
//验证元素的值是否为手机号码格式
case ValidateType.IsMobile:
if (null == value || value.ToString().Length < 1) break;
if (!System.Text.RegularExpressions.Regex.IsMatch(value.ToString(), @"^[1]+[3,5]+\d{9}$"))
validateResult = string.Format("元素 {0} 不是正确的手机号码格式", property.Name);
break;
//验证元素是否为空且符合指定的最小长度
case ValidateType.IsEmpty | ValidateType.MinLength:
if (null == value || value.ToString().Length < 1) goto case ValidateType.IsEmpty;
goto case ValidateType.MinLength;
//验证元素是否为空且符合指定的最大长度
case ValidateType.IsEmpty | ValidateType.MaxLength:
if (null == value || value.ToString().Length < 1) goto case ValidateType.IsEmpty;
goto case ValidateType.MaxLength;
//验证元素是否为空且符合指定的长度范围
case ValidateType.IsEmpty | ValidateType.MinLength | ValidateType.MaxLength:
if (null == value || value.ToString().Length < 1) goto case ValidateType.IsEmpty;
goto case ValidateType.MinLength | ValidateType.MaxLength;
//验证元素是否为空且值为数值型
case ValidateType.IsEmpty | ValidateType.IsNumber:
if (null == value || value.ToString().Length < 1) goto case ValidateType.IsEmpty;
goto case ValidateType.IsNumber;
//验证元素是否为空且值为浮点型
case ValidateType.IsEmpty | ValidateType.IsDecimal:
if (null == value || value.ToString().Length < 1) goto case ValidateType.IsEmpty;
goto case ValidateType.IsDecimal;
//验证元素是否为空且值为时间类型
case ValidateType.IsEmpty | ValidateType.IsDateTime:
if (null == value || value.ToString().Length < 1) goto case ValidateType.IsEmpty;
goto case ValidateType.IsDateTime;
//验证元素是否为空且值在指定的数据源中
case ValidateType.IsEmpty | ValidateType.IsInCustomArray:
if (null == value || value.ToString().Length < 1) goto case ValidateType.IsEmpty;
goto case ValidateType.IsInCustomArray;
//验证元素是否为空且值为固定电话号码格式
case ValidateType.IsEmpty | ValidateType.IsTelphone:
if (null == value || value.ToString().Length < 1) goto case ValidateType.IsEmpty;
goto case ValidateType.IsTelphone;
//验证元素是否为空且值为手机号码格式
case ValidateType.IsEmpty | ValidateType.IsMobile:
if (null == value || value.ToString().Length < 1) goto case ValidateType.IsEmpty;
goto case ValidateType.IsMobile;
default:
break;
}
}
}
if (!string.IsNullOrEmpty(validateResult))
break;
}
return validateResult;
}
最后,我们只需调用这么一句代码,就可以实现对整个实体类的元素的验证:
string checkMessage = AttributeHandle.GetValidateResult(orderRequest);
if(!string.IsNullOrEmpty(checkMessage))
return checkMessage;
//do something....
大功告成,整个页面清爽无比
附:由于公司的一些原因,我只能在基于.net framework2.0的VS2005上开发,是故后面的实现代码有些冗长
希望大家多提意见或建议,共同学习,共同进步!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异