Asp.net MVC中的提供很是簡單易用的數據驗證解決方案. 經過System.ComponentModel.DataAnnotations
提供的不少的驗證規則(Required, StringLength等)。可是經常有這樣的需求,咱們但願可以把model的驗證規則,保存到數據或者xml文件中,而不是代碼裏, 這樣的好處是,咱們能夠很方便的修改驗證規則和錯誤消息,避免須要從新發佈網站。mvc
這篇文章,一塊兒來看看是如何經過自定義ModelValidatorProvider來經過XML文件配置對於Model的驗證。app
閱讀目錄:ide
1、簡單回顧內置MVC驗證的使用網站
2、分析MVC驗證的內部過程ui
3、一個例子,針對ContactInfo的驗證spa
4、具體實現和應用XmlModelValidatorProvider.net
下面是典型的MVC驗證規則的代碼和頁面展現效果。code
1. 實際作驗證的是ModelValidatororm
當咱們如上圖,在爲Person類添加了各類驗證規則的dataannotation attributes後,實際操刀來作驗證的是DataAnnotationsModelValidator類.
DataAnnotationsModelValidator
繼承自抽象類ModelValidator,實現了抽象方法Validate, 在該方法中根據Person類中定義的驗證規則,對於全部Person的實例進行驗證,同時返回一個ModelValidationResult的集合。xml
2. ModelValidator是由ModelValidatorProviders提供的
MVC在驗證過程當中使用到的ModelValidator又是由ModelValidatorProviders類提供的, ModelValidatorProviders是一個抽象類,有個抽象方法GetValidators.
該類的定義是這樣的
namespace System.Web.Mvc { public abstract class ModelValidatorProvider { public abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context); } }
實際運行中,MVC使用的是繼承於ModelValidatorProvider, 實現了GetValidators方法的DataAnnotationsModelValidatorProvider類.
3. MVC的驗證過程當中能夠存在多個ModelValidatorProvider
MVC中能夠多個ModelValidatorProvider同時起做用, 他們的效果能夠疊加。咱們能夠使用默認的根據Attribute來定義驗證規則的DataAnnotationsModelValidatorProvider,也還能夠同時使用咱們下面的XmlModelValidatorProvider從xml文件中獲取驗證規則來作驗證。
下面這個ContactInfo類是咱們用來作實際驗證的,包含了一些經常使用的典型的驗證,Required, Email, Url等
public class ContactInfo { public string FirstName { get; set; } public string LastName{get;set;} public string Email{get;set;} public string Url{get;set;} }
下面的這個xml文件,定義的是關於ContactInfo類的驗證規則,之前咱們是寫在ContactInfo類中的,如今把它分離出來,放到Content\Validation\Rules\ContactInfo.xml文件中.
這裏的message中的值只是一個message的key, message的具體內容放在另一個xml文件中。
<?xml version="1.0" encoding="utf-8" ?> <model> <validator property="FirstName" type="Required" message="FirstName_Required" /> <validator property="FirstName" type="StringLength" arg-int="50" message="FirstName_Length" /> <validator property="LastName" type="Required" message="LastName_Required" /> <validator property="LastName" type="StringLength" arg-int="255" message="LastName_Length" /> <validator property="Email" type="Required" message="Email_Required" /> <validator property="Email" type="StringLength" arg-int="255" message="Email_Length" /> <validator property="Email" type="RegularExpression" arg="^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$" message="Email_RegularExpression" /> <validator property="Url" type="StringLength" arg-int="255" message="Url_Length" /> <validator property="Url" type="RegularExpression" arg="(http://)?(www\.)?\w+\.(com|net|edu|org)" message="Url_RegularExpression" /> </model>
存放message的是Content\Validation\Messages\ContactInfo.xml文件
<?xml version="1.0" encoding="utf-8" ?> <messages> <!-- filed error message --> <message key="FirstName_Required" text="The Frist Name field is required."></message> <message key="FirstName_Length" text="The field maximum length is 50"></message> <message key="LastName_Required" text="The Last Name field is required."></message> <message key="LastName_Length" text="The field maximum length is 255"></message> <message key="Email_Required" text="The Email field is required."></message> <message key="Email_Length" text="The field maximum length is 255"></message> <message key="Email_RegularExpression" text="Invalid email."></message> <message key="Url_Length" text="The field maximum length is 255"></message> <message key="Url_RegularExpression" text="Invalid URL."></message> </messages>
下面是咱們最重要的XmlModelValidatorProvider的實現代碼
public class XmlModelValidatorProvider : ModelValidatorProvider { // 用來保存System.ComponentModel.DataAnnotations中已經存在的驗證規則,也就是MVC自帶的Required等驗證規則, 由於咱們只是驗證規則的"源"不同,一個是代碼中,一個是xml,可是驗證過程是同樣的,因此要重用MVC中的已經寫好的驗證。 private readonly Dictionary<string, Type> _validatorTypes; private readonly string _xmlFolderPath = HttpContext.Current.Server.MapPath("~//Content//Validation//Rules"); public XmlModelValidatorProvider() {
_validatorTypes = Assembly.Load("System.ComponentModel.DataAnnotations").GetTypes() .Where(t => t.IsSubclassOf(typeof (ValidationAttribute))) .ToDictionary(t => t.Name, t => t); } #region Stolen from DataAnnotationsModelValidatorProvider // delegate that converts ValidationAttribute into DataAnnotationsModelValidator internal static DataAnnotationsModelValidationFactory DefaultAttributeFactory = (metadata, context, attribute) => new DataAnnotationsModelValidator(metadata, context, attribute); internal static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = new Dictionary <Type, DataAnnotationsModelValidationFactory> { { typeof (RangeAttribute), ( metadata, context, attribute) => new RangeAttributeAdapter (metadata, context, ( RangeAttribute ) attribute) }, { typeof (RegularExpressionAttribute), ( metadata, context, attribute) => new RegularExpressionAttributeAdapter (metadata, context, ( RegularExpressionAttribute ) attribute) }, { typeof (RequiredAttribute), ( metadata, context, attribute) => new RequiredAttributeAdapter (metadata, context, ( RequiredAttribute ) attribute) }, { typeof (StringLengthAttribute), ( metadata, context, attribute) => new StringLengthAttributeAdapter (metadata, context, ( StringLengthAttribute ) attribute) } }; #endregion // 重寫GetValidators方法,該從xml文件中獲取。根據xml的配置,返回對應的Validator集合 public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context) { var results = new List<ModelValidator>(); // whether the validation is for a property or model // (remember we can apply validation attributes to a property or model and same applies here as well) var isPropertyValidation = metadata.ContainerType != null && !String.IsNullOrEmpty(metadata.PropertyName); var rulesPath = String.Format("{0}\\{1}.xml", _xmlFolderPath, isPropertyValidation ? metadata.ContainerType.Name : metadata.ModelType.Name); var rules = File.Exists(rulesPath) ? XElement.Load(rulesPath).XPathSelectElements(String.Format( "./validator[@property='{0}']", isPropertyValidation ? metadata.PropertyName : metadata.ModelType.Name)).ToList() : new List<XElement>(); // Produce a validator for each validation attribute we find foreach (var rule in rules) { DataAnnotationsModelValidationFactory factory; var validatorType = _validatorTypes[String.Concat(rule.Attribute("type").Value, "Attribute")]; if (!AttributeFactories.TryGetValue(validatorType, out factory)) { factory = DefaultAttributeFactory; } var validator = (ValidationAttribute) Activator.CreateInstance(validatorType, GetValidationArgs(rule)); validator.ErrorMessage = rule.Attribute("message") != null && !String.IsNullOrEmpty(rule.Attribute("message").Value) ? GetValidationMessage(isPropertyValidation ? metadata.ContainerType.Name : metadata.ModelType.Name, rule.Attribute("message").Value) : null; results.Add(factory(metadata, context, validator)); } return results; } private string GetValidationMessage(string model, string key) { return MessageProvider.GetViewModelValidationMessage(model, key); } // read the arguments passed to the validation attribute and cast it their respective type. private object[] GetValidationArgs(XElement rule) { var validatorArgs = rule.Attributes().Where(a => a.Name.ToString().StartsWith("arg")); var args = new object[validatorArgs.Count()]; var i = 0; foreach (var arg in validatorArgs) { var argName = arg.Name.ToString(); var argValue = arg.Value; if (!argName.Contains("-")) { args[i] = argValue; } else { var argType = argName.Split('-')[1]; switch (argType) { case "int": args[i] = int.Parse(argValue); break; case "datetime": args[i] = DateTime.Parse(argValue); break; case "char": args[i] = Char.Parse(argValue); break; case "double": args[i] = Double.Parse(argValue); break; case "decimal": args[i] = Decimal.Parse(argValue); break; case "bool": args[i] = Boolean.Parse(argValue); break; default: args[i] = argValue; break; } } } return args; } }
最後,在Global.cs文件中,把XmlModelValidatorProvider添加到MVC的ModelValidatorProvidersCollection中
ModelValidatorProviders.Providers.Add(new XmlModelValidatorProvider());