Asp.net MVC 示例项目"Suteki.Shop"分析之---数据验证
2009-05-18 09:18
561 查看
在Suteki.Shop,实现了自己的数据校验机制,可以说其设计思路还是很有借鉴价值的。而使用
这种机制也很容易在Model中对相应的实体对象(属性)添加校验操作方法。下面就来介绍一下其实
现方式。
首先,看一下这样类图:
public static class ValidationExtensions
{
public static ValidationProperty<T> Label<T>(this T value, string label)
{
return new ValidationProperty<T>(value, label);
}
}
public class ValidationProperty<T>
{
T value;
string label;
public ValidationProperty(T value, string label)
{
this.value = value;
this.label = label;
}
public bool IsObject
{
get { return typeof (object).IsAssignableFrom(typeof (T)); }
}
public bool IsString
{
get { return (IsObject && typeof (string).IsAssignableFrom(typeof (T))); }
}
public string ValueAsString
{
get { return value as string; }
}
public T Value
{
get { return value; }
}
public ValidationProperty<T> IsRequired()
{
if (value == null)
{
throw new ValidationException(label, StringExtensions.With("You must enter a value for {0}", label));
}
if (IsString && string.IsNullOrEmpty(ValueAsString))
{
throw new ValidationException(label, StringExtensions.With("You must enter a value for {0}", label));
}
return this;
}
public ValidationProperty<T> IsNumeric()
{
if(IsString)
{
// the obvious thing (int.Parse) doesn't work for very long strings of digits
if (ValueAsString.Trim().Any(c => !char.IsDigit(c)))
{
throw new ValidationException(label,"{0} must be a number e.g. 240".With(label));
}
}
return this;
}
public ValidationProperty<T> IsDecimal()
{
if (IsString && ValueAsString.Trim().Any(c => !(char.IsDigit(c) || c == '.')))
{
throw new ValidationException(label,"{0} must be a decimal number e.g 12.30".With(label));
}
return this;
}
public ValidationProperty<T> IsNonZero()
{
int test;
if (!int.TryParse(value.ToString(), out test))
{
throw new ValidationException(label,"{0} must be a non-zero number".With(label));
}
if (test == 0)
{
throw new ValidationException(label,"{0} must be non-zero".With(label));
}
return this;
}
public ValidationProperty<T> WithMaxLength(int maxLength)
{
if (IsString && ValueAsString.Length > maxLength)
{
throw new ValidationException(label,"{0} must not exceed {1} characters".With(label, maxLength));
}
return this;
}
public ValidationProperty<T> WithLengthRange(IEnumerable<int> range)
{
if (IsString && ValueAsString.Length < range.Min() || ValueAsString.Length > range.Max())
{
throw new ValidationException(label,
"{0} length must be between {1} and {2} characters".With(label, range.Min(), range.Max()));
}
return this;
}
public ValidationProperty<T> IsEmail()
{
// ignore is null or empty, use IsRequired in parrallel to check this if needed
if (!IsString) return this;
if (string.IsNullOrEmpty(ValueAsString)) return this;
const string patternLenient = @"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*";
if (!Regex.Match(ValueAsString, patternLenient).Success)
{
throw new ValidationException(label,"{0} must be a valid email address".With(label));
}
return this;
}
public ValidationProperty<T> IsCreditCard()
{
if (IsString)
{
var trimmedValue = Regex.Replace(value.ToString(), "[^0-9]", "");
trimmedValue.Label(label).IsNumeric().WithLengthRange(13.To(19));
var numbers = trimmedValue.Trim().Reverse().Select(c => int.Parse(c.ToString()));
var oddSum = numbers.AtOddPositions().Sum();
var doubleEvenSum = numbers.AtEvenPositions().SelectMany(i => new[] { (i * 2) % 10, (i * 2) / 10 }).Sum();
if ((oddSum + doubleEvenSum) % 10 != 0)
{
throw new ValidationException(label, "{0} is not a valid credit card number".With(label));
}
}
return this;
}
}
使用它就可以很方便的对Model中的相关属性添加验证规则了。以User为例,其验证规则添加
内容如下(Suteki.Shop\Models\User.cs):
public void Validate()
{
Validator validator = new Validator
{
() => Email.Label("Email").IsRequired().IsEmail(),
() => Password.Label("Password").IsRequired(),
};
validator.Validate();
}
在规则添加完成后,就把对获取到的信息进行验证了,下面是验证的实现方法:
public class Validator : List<Action>
{
public void Validate()
{
var errors = new List<ValidationException>();
foreach (Action validation in this)
{
try
{
validation();
}
catch (ValidationException validationException)
{
errors.Add(validationException);
}
}
if (errors.Count > 0)
{
//backwards compatibility
string error = string.Join("", errors.Select(x => x.Message + "<br />").ToArray());
throw new ValidationException(error, errors);
}
}
}
代码比较简单,大家看一下就可以了。
到这里,主要的代码就介绍完了,下面再后到UserController中看看Action是如何调用验证方法
并发验证错误信息复制到ModelState中的,接着看一下编辑用户信息这个Action:
[AcceptVerbs(HttpVerbs.Post), UnitOfWork]
public ActionResult Edit([DataBind] User user, string password)
{
if(! string.IsNullOrEmpty(password))
{
user.Password = userService.HashPassword(password);
}
try
{
user.Validate();
}
catch (ValidationException validationException)
{
validationException.CopyToModelState(ModelState, "user");
return View("Edit", EditViewData.WithUser(user));
}
return View("Edit", EditViewData.WithUser(user).WithMessage("Changes have been saved"));
}
大家看到了吧,Try中的user.Validate()就是启动验证的功能,而在Catch中使用CopyToModelState
方法将错误信息Copy到当前Controller中的ModelState中,如下:
public void CopyToModelState(ModelStateDictionary dictionary, string prefix)
{
foreach(var error in errors)
{
string key = string.IsNullOrEmpty(prefix) ? error.propertyKey : prefix + "." + error.propertyKey;
dictionary.AddModelError(key, error.Message);
}
}
这样在前台View中,通过Html.ValidationSummary()方法来显示验证结果,现在我们看一下最终的
运行效果:
以“输入错误的Email地址”为例:
好了,今天的内容就先到这里了。
原文链接:/article/4598195.html
作者: daizhj,代震军,LaoD
Tags: mvc,Suteki.Shop
网址: http://daizhj.cnblogs.com/
这种机制也很容易在Model中对相应的实体对象(属性)添加校验操作方法。下面就来介绍一下其实
现方式。
首先,看一下这样类图:
public static class ValidationExtensions
{
public static ValidationProperty<T> Label<T>(this T value, string label)
{
return new ValidationProperty<T>(value, label);
}
}
public class ValidationProperty<T>
{
T value;
string label;
public ValidationProperty(T value, string label)
{
this.value = value;
this.label = label;
}
public bool IsObject
{
get { return typeof (object).IsAssignableFrom(typeof (T)); }
}
public bool IsString
{
get { return (IsObject && typeof (string).IsAssignableFrom(typeof (T))); }
}
public string ValueAsString
{
get { return value as string; }
}
public T Value
{
get { return value; }
}
public ValidationProperty<T> IsRequired()
{
if (value == null)
{
throw new ValidationException(label, StringExtensions.With("You must enter a value for {0}", label));
}
if (IsString && string.IsNullOrEmpty(ValueAsString))
{
throw new ValidationException(label, StringExtensions.With("You must enter a value for {0}", label));
}
return this;
}
public ValidationProperty<T> IsNumeric()
{
if(IsString)
{
// the obvious thing (int.Parse) doesn't work for very long strings of digits
if (ValueAsString.Trim().Any(c => !char.IsDigit(c)))
{
throw new ValidationException(label,"{0} must be a number e.g. 240".With(label));
}
}
return this;
}
public ValidationProperty<T> IsDecimal()
{
if (IsString && ValueAsString.Trim().Any(c => !(char.IsDigit(c) || c == '.')))
{
throw new ValidationException(label,"{0} must be a decimal number e.g 12.30".With(label));
}
return this;
}
public ValidationProperty<T> IsNonZero()
{
int test;
if (!int.TryParse(value.ToString(), out test))
{
throw new ValidationException(label,"{0} must be a non-zero number".With(label));
}
if (test == 0)
{
throw new ValidationException(label,"{0} must be non-zero".With(label));
}
return this;
}
public ValidationProperty<T> WithMaxLength(int maxLength)
{
if (IsString && ValueAsString.Length > maxLength)
{
throw new ValidationException(label,"{0} must not exceed {1} characters".With(label, maxLength));
}
return this;
}
public ValidationProperty<T> WithLengthRange(IEnumerable<int> range)
{
if (IsString && ValueAsString.Length < range.Min() || ValueAsString.Length > range.Max())
{
throw new ValidationException(label,
"{0} length must be between {1} and {2} characters".With(label, range.Min(), range.Max()));
}
return this;
}
public ValidationProperty<T> IsEmail()
{
// ignore is null or empty, use IsRequired in parrallel to check this if needed
if (!IsString) return this;
if (string.IsNullOrEmpty(ValueAsString)) return this;
const string patternLenient = @"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*";
if (!Regex.Match(ValueAsString, patternLenient).Success)
{
throw new ValidationException(label,"{0} must be a valid email address".With(label));
}
return this;
}
public ValidationProperty<T> IsCreditCard()
{
if (IsString)
{
var trimmedValue = Regex.Replace(value.ToString(), "[^0-9]", "");
trimmedValue.Label(label).IsNumeric().WithLengthRange(13.To(19));
var numbers = trimmedValue.Trim().Reverse().Select(c => int.Parse(c.ToString()));
var oddSum = numbers.AtOddPositions().Sum();
var doubleEvenSum = numbers.AtEvenPositions().SelectMany(i => new[] { (i * 2) % 10, (i * 2) / 10 }).Sum();
if ((oddSum + doubleEvenSum) % 10 != 0)
{
throw new ValidationException(label, "{0} is not a valid credit card number".With(label));
}
}
return this;
}
}
使用它就可以很方便的对Model中的相关属性添加验证规则了。以User为例,其验证规则添加
内容如下(Suteki.Shop\Models\User.cs):
public void Validate()
{
Validator validator = new Validator
{
() => Email.Label("Email").IsRequired().IsEmail(),
() => Password.Label("Password").IsRequired(),
};
validator.Validate();
}
在规则添加完成后,就把对获取到的信息进行验证了,下面是验证的实现方法:
public class Validator : List<Action>
{
public void Validate()
{
var errors = new List<ValidationException>();
foreach (Action validation in this)
{
try
{
validation();
}
catch (ValidationException validationException)
{
errors.Add(validationException);
}
}
if (errors.Count > 0)
{
//backwards compatibility
string error = string.Join("", errors.Select(x => x.Message + "<br />").ToArray());
throw new ValidationException(error, errors);
}
}
}
代码比较简单,大家看一下就可以了。
到这里,主要的代码就介绍完了,下面再后到UserController中看看Action是如何调用验证方法
并发验证错误信息复制到ModelState中的,接着看一下编辑用户信息这个Action:
[AcceptVerbs(HttpVerbs.Post), UnitOfWork]
public ActionResult Edit([DataBind] User user, string password)
{
if(! string.IsNullOrEmpty(password))
{
user.Password = userService.HashPassword(password);
}
try
{
user.Validate();
}
catch (ValidationException validationException)
{
validationException.CopyToModelState(ModelState, "user");
return View("Edit", EditViewData.WithUser(user));
}
return View("Edit", EditViewData.WithUser(user).WithMessage("Changes have been saved"));
}
大家看到了吧,Try中的user.Validate()就是启动验证的功能,而在Catch中使用CopyToModelState
方法将错误信息Copy到当前Controller中的ModelState中,如下:
public void CopyToModelState(ModelStateDictionary dictionary, string prefix)
{
foreach(var error in errors)
{
string key = string.IsNullOrEmpty(prefix) ? error.propertyKey : prefix + "." + error.propertyKey;
dictionary.AddModelError(key, error.Message);
}
}
这样在前台View中,通过Html.ValidationSummary()方法来显示验证结果,现在我们看一下最终的
运行效果:
以“输入错误的Email地址”为例:
好了,今天的内容就先到这里了。
原文链接:/article/4598195.html
作者: daizhj,代震军,LaoD
Tags: mvc,Suteki.Shop
网址: http://daizhj.cnblogs.com/
相关文章推荐
- Asp.net MVC 示例项目"Suteki.Shop"分析之---数据验证
- Asp.net MVC 示例项目"Suteki.Shop"分析之---数据验证
- Asp.net MVC 示例项目"Suteki.Shop"分析之---数据验证
- Asp.net MVC 示例项目"Suteki.Shop"分析之---安装篇
- Asp.net MVC 示例项目"Suteki.Shop"分析之---Filter 推荐
- Asp.net MVC 示例项目"Suteki.Shop"分析之---IOC(控制反转) 推荐
- Asp.net MVC 示例项目"Suteki.Shop"分析之---Model和Service
- Asp.net MVC 示例项目"Suteki.Shop"分析之---NHibernate
- Asp.net MVC 示例项目"Suteki.Shop"分析之---结束篇
- Asp.net MVC 示例项目"Suteki.Shop"分析之---安装篇
- Asp.net MVC 示例项目"Suteki.Shop"分析之---Controller
- Asp.net MVC 示例项目"Suteki.Shop"分析之---Filter
- Asp.net MVC 示例项目"Suteki.Shop"分析之---ViewData
- Asp.net MVC 示例项目"Suteki.Shop"分析之---NVelocity模版引擎 推荐
- Asp.net MVC 示例项目"Suteki.Shop"分析之---结束篇
- Asp.net MVC 示例项目"Suteki.Shop"分析之---ModelBinder
- Asp.net MVC 示例项目"Suteki.Shop"分析之---IOC(控制反转)
- Asp.net MVC 示例项目"Suteki.Shop"分析之---Model和Service
- Asp.net MVC 示例项目"Suteki.Shop"分析之---NVelocity模版引擎
- Asp.net MVC 示例项目"Suteki.Shop"分析之---NHibernate