梳理需求
关于这个想法,想看看历史背景的,请移步 《数据验证随想》
今天要说的是这个组件在项目中的应用,以及后期对它的改善。在原来的想法中,希望做的大而全,结果越往后做,发现有点过度设计,写完代码后,楼主自己都懒得用。与其这么麻烦,还不如直接几个 if 判断完事。
既然如此,只能将问题简单化。认真梳理一下楼主的需求:
1. 部分参数是必需的,验证失败,中断,并返回该错误信息。
2. 部分参数是非必需的。验证失败就直接忽略。这个需求来自于,表单里部分值是可以空的,或者传递了意外的值,试图忽略它。
3. 验证成功后的参数,最好是能直接使用,而且类型都是转换后的。
解决方案
先看看代码上是怎么调用的。
var v = ValidateHelp.BeginEntity<PumpCodeEntity>() .IsNullOrEmpty(form["tybh"], "统一编号", "未找到需要修改的数据") .IsInt(form["zldm"], "指令代码", "指令代码错误") .IsInt(form["sfyx"], "是否有效", "有效指令错误"); if (v.IsInValid) //数据无效 { return new ResultDTO() { Message = v.Message}; } ......
其中form是MVC的FormCollection对象,没用过的理解成HttpRequest也成。
PumpCodeEntity 是实体对象,呃,其中 “统一编号、指令代码、是否有效” 是这个对象的属性。为什么它会是中文,这个有项目背景,重点不是它啦。
大家可以看到,其中有3个验证方法,方法带有3个参数,分别是:
待验证的值
Key名或属性名(验证成功存储在Dictionary中, 或通过反射存储在对象中)
错误消息提示 当未传递错误信息参数时,表示当前验证的参数是非必需的。由于在验证参数时,尝试对参数做了类型转换,故验证成功后,参数的类型是正确的,将参数存储到Dictionary<string,object>中,或者是对象的属性中。
因此增加检测条件时,只需要增加一个类型的判断方法即可。代码中已经内置了检测整数、浮点数、双精度、时间、正则等。
详细代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Text.RegularExpressions; 6 using CheckHandle = System.Func<Core.Data.ValidateHelp, string, string, string, Core.Data.ValidateHelp>; 7 namespace Core.Data 8 { 9 public class ValidateHelp 10 { 11 #region 属性 12 //数据容器字典 13 public Dictionary<string, object> Data { get; protected set; } 14 //数据容器实体 15 public Object Entity { get; protected set; } 16 //错误信息 17 public string Message { get; protected set; } 18 /// <summary> 19 /// true:数据无效 false:数据有效 20 /// </summary> 21 private bool isInValid; 22 /// <summary> 23 /// true:数据无效 false:数据有效 24 /// </summary> 25 public bool IsInValid { get { return isInValid; } } 26 #endregion 27 28 #region 字段 29 30 //数据容器实体类型 31 protected Type EntityType; 32 //验证委托缓存 33 private static Dictionary<string, CheckHandle> checkHandlers = new Dictionary<string, CheckHandle>(); 34 35 #endregion 36 37 #region 静态方法 得到对象实例 38 public static ValidateHelp Begin() 39 { 40 var v = new ValidateHelp(); 41 v.Data = new Dictionary<string, object>(); 42 return v; 43 } 44 45 public static ValidateHelp BeginEntity<T>() 46 { 47 var v = new ValidateHelp(); 48 v.EntityType = typeof(T); 49 v.Entity = Activator.CreateInstance(v.EntityType); 50 return v; 51 } 52 #endregion 53 54 #region 私有辅助方法 55 private ValidateHelp() 56 { 57 } 58 59 60 private static ValidateHelp Error(ValidateHelp v, string msg) 61 { 62 //没有错误信息 默认不修改验证结果 63 if (msg == null || msg.Length == 0) 64 { 65 return v; 66 } 67 v.isInValid = true; 68 v.Message = msg; 69 return v; 70 } 71 72 private static ValidateHelp Success(ValidateHelp v, object value, string propertyName) 73 { 74 if (v.IsInValid) return v; 75 if (propertyName != null && propertyName.Length > 0) 76 { 77 if (v.Data != null) 78 { 79 v.Data[propertyName] = value; 80 } 81 else if (v.Entity != v) 82 { 83 var p = v.Entity.GetType().GetProperty(propertyName); 84 //为对象属性赋值 85 p.SetValue(v.Entity, value, null); 86 } 87 } 88 return v; 89 } 90 91 private ValidateHelp Check(string[] args, CheckHandle fun) 92 { 93 if (args == null || args.Length == 0 || args.Length > 3) 94 { 95 throw new Exception("验证参数错误"); 96 } 97 //如果之前验证失败,就直接返回 98 if (isInValid) return this; 99 var value = args[0]; 100 var propertyName = args.Length > 1 ? args[1] : null; 101 var msg = args.Length > 2 ? args[2] : null; 102 return fun(this, value, propertyName, msg); 103 } 104 105 #endregion 106 107 #region 公有辅助方法 108 public object this[string key] 109 { 110 get 111 { 112 object o; 113 if (Data.TryGetValue(key, out o)) return o; 114 return null; 115 } 116 } 117 #endregion 118 119 #region 验证方法 120 public virtual ValidateHelp IsNullOrEmpty(params string[] args) 121 { 122 CheckHandle handler; 123 if (!checkHandlers.TryGetValue("IsNullOrEmpty", out handler)) 124 { 125 handler = (scope, s, p, m) => 126 { 127 if (s == null || s.Length == 0) 128 { 129 return Error(scope, m); 130 } 131 return Success(scope, s, p); 132 }; 133 checkHandlers["IsNullOrEmpty"] = handler; 134 }; 135 return Check(args, handler); 136 } 137 public virtual ValidateHelp IsInt(params string[] args) 138 { 139 CheckHandle handler; 140 if (!checkHandlers.TryGetValue("IsInt", out handler)) 141 { 142 handler = (scope, s, p, m) => 143 { 144 int i; 145 if (!int.TryParse(s, out i)) 146 { 147 return Error(scope, m); 148 } 149 return Success(scope, i, p); 150 }; 151 checkHandlers["IsInt"] = handler; 152 }; 153 return Check(args, handler); 154 } 155 156 public virtual ValidateHelp IsDouble(params string[] args) 157 { 158 CheckHandle handler; 159 if (!checkHandlers.TryGetValue("IsDouble", out handler)) 160 { 161 handler = (scope, s, p, m) => 162 { 163 double i; 164 if (!double.TryParse(s, out i)) 165 { 166 return Error(scope, m); 167 } 168 return Success(scope, i, p); 169 }; 170 checkHandlers["IsDouble"] = handler; 171 }; 172 return Check(args, handler); 173 } 174 175 public virtual ValidateHelp IsLong(params string[] args) 176 { 177 CheckHandle handler; 178 if (!checkHandlers.TryGetValue("IsLong", out handler)) 179 { 180 handler = (scope, s, p, m) => 181 { 182 long i; 183 if (!long.TryParse(s, out i)) 184 { 185 return Error(scope, m); 186 } 187 return Success(scope, i, p); 188 }; 189 checkHandlers["IsLong"] = handler; 190 }; 191 return Check(args, handler); 192 } 193 194 public virtual ValidateHelp IsFloat(params string[] args) 195 { 196 CheckHandle handler; 197 if (!checkHandlers.TryGetValue("IsFloat", out handler)) 198 { 199 handler = (scope, s, p, m) => 200 { 201 float i; 202 if (!float.TryParse(s, out i)) 203 { 204 return Error(scope, m); 205 } 206 return Success(scope, i, p); 207 }; 208 checkHandlers["IsFloat"] = handler; 209 }; 210 return Check(args, handler); 211 } 212 213 public virtual ValidateHelp IsBool(params string[] args) 214 { 215 CheckHandle handler; 216 if (!checkHandlers.TryGetValue("IsBool", out handler)) 217 { 218 handler = (scope, s, p, m) => 219 { 220 bool i; 221 if (!bool.TryParse(s, out i)) 222 { 223 return Error(scope, m); 224 } 225 return Success(scope, i, p); 226 }; 227 checkHandlers["IsBool"] = handler; 228 }; 229 return Check(args, handler); 230 } 231 232 public virtual ValidateHelp IsDateTime(params string[] args) 233 { 234 CheckHandle handler; 235 if (!checkHandlers.TryGetValue("IsDateTime", out handler)) 236 { 237 handler = (scope, s, p, m) => 238 { 239 DateTime i; 240 if (!DateTime.TryParse(s, out i)) 241 { 242 return Error(scope, m); 243 } 244 return Success(scope, i, p); 245 }; 246 checkHandlers["IsDateTime"] = handler; 247 }; 248 return Check(args, handler); 249 } 250 251 public virtual ValidateHelp IsEnum<T>(params string[] args) where T : struct 252 { 253 CheckHandle handler; 254 if (!checkHandlers.TryGetValue("IsEnum", out handler)) 255 { 256 handler = (scope, s, p, m) => 257 { 258 T i; 259 if (!Enum.TryParse(s, true, out i)) 260 { 261 return Error(scope, m); 262 } 263 return Success(scope, i, p); 264 }; 265 checkHandlers["IsEnum"] = handler; 266 }; 267 return Check(args, handler); 268 } 269 270 public virtual ValidateHelp IsRegex(string pattern, params string[] args) 271 { 272 CheckHandle handler; 273 if (!checkHandlers.TryGetValue(pattern, out handler)) 274 { 275 handler = (scope, s, p, m) => 276 { 277 if (!Regex.IsMatch(s, pattern)) 278 { 279 return Error(scope, m); 280 } 281 return Success(scope, s, p); 282 }; 283 checkHandlers[pattern] = handler; 284 }; 285 return Check(args, handler); 286 } 287 288 public virtual ValidateHelp IsChar(params string[] args) 289 { 290 return IsRegex(@"^[A-Za-z]+$", args); 291 } 292 293 public virtual ValidateHelp IsNumber(params string[] args) 294 { 295 return IsRegex(@"^d+$", args); 296 } 297 298 public virtual ValidateHelp IsChinese(params string[] args) 299 { 300 return IsRegex(@"^[u4e00-u9fa5]+$", args); 301 } 302 303 public virtual ValidateHelp IsEmail(params string[] args) 304 { 305 return IsRegex(@"^[w-]+(.[w-]+)*@[w-]+(.[w-]+)+$", args); 306 } 307 308 public virtual ValidateHelp IsIP(params string[] args) 309 { 310 return IsRegex(@"^((2[0-4]d|25[0-5]|[01]?dd?).){3}(2[0-4]d|25[0-5]|[01]?dd?)$", args); 311 } 312 313 public virtual ValidateHelp IsUrl(params string[] args) 314 { 315 return IsRegex(@"^([a-zA-z]+://)?(w+(-w+)*)(.(w+(-w+)*))*(?S*)?$", args); 316 } 317 318 public virtual ValidateHelp IsPhone(params string[] args) 319 { 320 return IsRegex(@"((d{11})|^((d{7,8})|(d{4}|d{3})-(d{7,8})|(d{4}|d{3})-(d{7,8})-(d{4}|d{3}|d{2}|d{1})|(d{7,8})-(d{4}|d{3}|d{2}|d{1}))$)", args); 321 } 322 #endregion 323 } 324 }
其中验证方法使用了委托,用字典缓存委托。
最后
有个问题,一直找不到好的方式解决,比如验证了10个参数,第1个的时候就验证失败,除了抛出异常,楼主暂时想不到什么方法可以中断后面的验证,但是抛异常带来的代码量非常大,因此折衷方案是,在具体的验证逻辑中,若已经验证失败,则直接跳过。
将属性值赋值到实体上,使用的方式是反射,这一块可以优化一下,可以看看 《[源码]Literacy 快速反射读写对象属性,字段》 。使用反射的时候,属性名是大小写敏感的,而这个Literacy是可以设置大小写不敏感。
示例项目是临时写的,验证代码在Default.aspx.cs里。