MVCデータ検証原理及びカスタムModelValidatorProviderによる無コンパイル修正検証規則とエラー情報の実現

19428 ワード

Asp.NetMVCでは、非常に使いやすいデータ検証ソリューションを提供しています.System.ComponentModel.DataAnnotationsによって提供される多くの検証規則(Required,StringLengthなど).しかし、コードではなくmodelの検証ルールをデータやxmlファイルに保存する必要があることがよくあります.このようなメリットは、検証ルールやエラーメッセージを簡単に修正し、Webサイトを再公開する必要がないようにすることです.
この記事では、ModelValidatorProviderをカスタマイズしてXMLファイル構成でModelの検証を行う方法について説明します.
目次を読む:
一、内蔵MVC検証の使用を簡単に振り返る
二、MVC検証の内部過程を分析する
三、一例、ContactInfoの検証
四、XmlModelValidatorProviderの具体的な実現と応用
一、内蔵MVC検証の使用を簡単に振り返る MVC 。

, MVC

1. ModelValidator

, Person dataannotation attributes , DataAnnotationsModelValidator .
DataAnnotationsModelValidator クラスModelValidatorから され、Personクラスで された ルールに づいて、すべてのPersonのインスタンスを するとともに、ModelValidationResultのセットを す メソッドValidateが されます.
2.ModelValidatorはModelValidatorProvidersが する
MVCが に するModelValidatorはまたModelValidatorProvidersクラスによって され、ModelValidatorProvidersは クラスであり、 な GetValidatorsがある.
クラスの はこうです

namespace System.Web.Mvc
{
    public abstract class ModelValidatorProvider
    {
        public abstract IEnumerable GetValidators(ModelMetadata metadata, ControllerContext context);
    }
}

の ではMVCはModelValidatorProviderに するGetValidatorsメソッドを したDataAnnotationsModelValidatorProviderクラスを いる.
 
3.MVCの に のModelValidatorProviderが することができる
MVCでは のModelValidatorProviderが に し,それらの を ねることができる.デフォルトのAttributeに づいて ルールを するD a t a n o t i o n s ModelValidatorProviderを するか、 のXmlModelValidatorProviderを してxmlファイルから ルールを して することもできます.
 
、 、ContactInfoに する
の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クラスに いていましたが、 はそれを してContentValidationRulesContactInfoに きます.xmlファイルで
ここでのmessageの は1つのmessageのkeyにすぎず、messageの な は のxmlファイルに かれています.
"1.0" encoding="utf-8" ?>

  "FirstName" type="Required"  message="FirstName_Required" />
  "FirstName" type="StringLength" arg-int="50" message="FirstName_Length" />
  "LastName" type="Required"  message="LastName_Required" />
  "LastName" type="StringLength" arg-int="255" message="LastName_Length" />
  "Email" type="Required"  message="Email_Required" />
  "Email" type="StringLength" arg-int="255" message="Email_Length" />
  "Email" type="RegularExpression" arg="^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$" message="Email_RegularExpression" />
  "Url" type="StringLength" arg-int="255" message="Url_Length" />
  "Url" type="RegularExpression" arg="(http://)?(www\.)?\w+\.(com|net|edu|org)" message="Url_RegularExpression" />
 

メッセージを するのはContentValidationMessageContactInfoです.xmlファイル
"1.0" encoding="utf-8" ?>

  
  "FirstName_Required" text="The Frist Name field is required.">
  "FirstName_Length" text="The field maximum length is 50">
  "LastName_Required" text="The Last Name field is required.">
  "LastName_Length" text="The field maximum length is 255">
  "Email_Required" text="The Email field is required.">
  "Email_Length" text="The field maximum length is 255">
  "Email_RegularExpression" text="Invalid email.">
  "Url_Length" text="The field maximum length is 255">
  "Url_RegularExpression" text="Invalid URL.">
 

、XmlModelValidatorProviderの な と
は たちの も な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 AttributeFactories = new Dictionary { { 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 GetValidators(ModelMetadata metadata, ControllerContext context) { var results = new List(); // 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(); // 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());