码迷,mamicode.com
首页 > 其他好文 > 详细

Model的验证

时间:2015-06-10 22:29:26      阅读:256      评论:0      收藏:0      [点我收藏+]

标签:

ModelValidator与ModelValidatorProvider

ModelValidator

public abstract class ModelValidator
{

    public virtual bool IsRequired
    {

        get { return false; }

    }

    public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules()//用于客户端验证
    {

        return Enumerable.Empty<ModelClientValidationRule>();

    }

    public abstract IEnumerable<ModelValidationResult> Validate(object container);//用于服务端验证

    public static ModelValidator GetModelValidator(ModelMetadata metadata, ControllerContext context)
    {

        return new CompositeModelValidator(metadata, context);

    }

}

ModelValidationResult集合只有在验证失败的情况下才会返回。验证通过的情况下,返回值时空集合

  • ModelValidationResult
public class ModelValidationResult
{

    private string _memberName;

    private string _message;

    public string MemberName
    {

        get { return _memberName ?? String.Empty; }

        set { _memberName = value; }

    }

    public string Message
    {

        get { return _message ?? String.Empty; }

        set { _message = value; }

    }

}
  • ModelClientValidationRule表示客户端验证规则,在HTML中辅助js进行客户端验证
public class ModelClientValidationRule
{

    private readonly Dictionary<string, object> _validationParameters = new Dictionary<string, object>();

    private string _validationType;

    public string ErrorMessage { get; set; }

    public IDictionary<string, object> ValidationParameters
    {

        get { return _validationParameters; }

    }

    public string ValidationType
    {

        get { return _validationType ?? String.Empty; }

        set { _validationType = value; }

    }

}
  • CompositeModelValidator内部类,有ModelValidator的静态方法GetModelValidator返回.

当CompositeModelValidator 被用于验证一个容器对象的时候,会先验证其属性成员。针对容器对象自身的验证只有在所有属性值都通过验证的情况下才会进行

private class CompositeModelValidator : ModelValidator
{

    public CompositeModelValidator(ModelMetadata metadata, ControllerContext controllerContext)

        : base(metadata, controllerContext)
    {

    }

    public override IEnumerable<ModelValidationResult> Validate(object container)
    {

        bool propertiesValid = true;

        foreach (ModelMetadata propertyMetadata in Metadata.Properties) {

            foreach (ModelValidator propertyValidator in propertyMetadata.GetValidators(ControllerContext)) {

                foreach (ModelValidationResult propertyResult in propertyValidator.Validate(Metadata.Model)) {

                    propertiesValid = false;

                    yield return new ModelValidationResult {

                        MemberName = DefaultModelBinder.CreateSubPropertyName(propertyMetadata.PropertyName, propertyResult.MemberName),

                        Message = propertyResult.Message

                    };

                }

            }

        }

        if (propertiesValid) {

            foreach (ModelValidator typeValidator in Metadata.GetValidators(ControllerContext)) {

                foreach (ModelValidationResult typeResult in typeValidator.Validate(container)) {

                    yield return typeResult;

                }

            }

        }

    }

}
  • DataAnnotationsModelValidator数据注解验证特性,最为常用的一种Model验证方案、

public class DataAnnotationsModelValidator : ModelValidator后边会有详细的介绍

  • ClientModelValidator 用于客户端的验证,是一个内部类.其用于服务端验证的Validate方法返回空的集合.

NurnericModelValidator和DateModelValidator是其继承者

  • DataErrorlnfoModelValidator
    1. IDataErrorInfo
    2. public interface IDataErrorInfo
      {
      
          string Error { get; }//基于自身的错误消息
      
          string this[string columnName] { get; }//数据成员的错误消息
      
      }
    3. DataErrorInfoClassModelValidator
    4. DataErrorInfoPropertyModelValidator
  • ValidatableObjectAdapter将自我验证的结果转换为通用的格式
    • IValidatableObject自我验证.

ModelValidatorProvider

ASPNET MVC多采用Provider的方式提供组件.

public abstract class ModelValidatorProvider
{//只有这一个方法

    public abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);

}
  • DataAnnotationsModelValidatorProvider
public class DataAnnotationsModelValidatorProvider : AssociatedValidatorProvider
public abstract class AssociatedValidatorProvider : ModelValidatorProvider
{

    protected virtual ICustomTypeDescriptor GetTypeDescriptor(Type type)
    {

        return TypeDescriptorHelper.Get(type);

    }

    public sealed override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
    {

        if (metadata == null) {

            throw new ArgumentNullException("metadata");

        }

        if (context == null) {

            throw new ArgumentNullException("context");

        }

        if (metadata.ContainerType != null && !String.IsNullOrEmpty(metadata.PropertyName)) {

            return GetValidatorsForProperty(metadata, context);

        }

        return GetValidatorsForType(metadata, context);

    }

    protected abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes);

    private IEnumerable<ModelValidator> GetValidatorsForProperty(ModelMetadata metadata, ControllerContext context)
    {//获取在父容器中的属性上的特性和数据类型上 的特性的列表.

        ICustomTypeDescriptor typeDescriptor = GetTypeDescriptor(metadata.ContainerType);

        PropertyDescriptor property = typeDescriptor.GetProperties().Find(metadata.PropertyName, true);

        if (property == null) {

            throw new ArgumentException(

            String.Format(

            CultureInfo.CurrentCulture,

            MvcResources.Common_PropertyNotFound,

            metadata.ContainerType.FullName, metadata.PropertyName),

            "metadata");

        }

        return GetValidators(metadata, context, property.Attributes.OfType<Attribute>());

    }

    private IEnumerable<ModelValidator> GetValidatorsForType(ModelMetadata metadata, ControllerContext context)
    {//获取数据类型上的特性列表

        return GetValidators(metadata, context, GetTypeDescriptor(metadata.ModelType).GetAttributes().Cast<Attribute>());

    }

}
  • ClientDataTypeModelValidatorProvider

针对数字/日期类型客户端验证的NumericModelValidator 和DateModelValidator 最终是通过具有如下定义的System.Web.Mvc.ClientDataTypeModelValidatorProvider 来提供的。在实现的GetValidators 方法中,它会根据指定ModelMetadata 判断被验证类型是否属于数字/DateTime 类型,如果是则直接返回一个包含单个NumericModelValidator 或者DateModelValidator对象的ModelValidator 集合。在这里被视为数字的类型包括byte 、sbyte 、short 、ushort 、int 、uint 、long 、ulong 、float 、double 和decimal 等。

public class ClientDataTypeModelValidatorProvider : ModelValidatorProvider
{

    private static readonly HashSet<Type> _numericTypes = new HashSet<Type>(new Type[]

    {

    typeof(byte), typeof(sbyte),

    typeof(short), typeof(ushort),

    typeof(int), typeof(uint),

    typeof(long), typeof(ulong),

    typeof(float), typeof(double), typeof(decimal)

    });

    //判断数据类型,返回Date或者Num的Validator

    private static IEnumerable<ModelValidator> GetValidatorsImpl(ModelMetadata metadata, ControllerContext context)
    {

        Type type = metadata.ModelType;

        if (IsDateTimeType(type, metadata)) {

            yield return new DateModelValidator(metadata, context);

        }

        if (IsNumericType(type)) {

            yield return new NumericModelValidator(metadata, context);

        }

    }

}
  • DataErrorlnfoModelValidatorProvider

旨在对实现了IDataErrorInfo 接口的数据实施验证的两个DataErrorI nfoModelValidator(即DataErrorI nfoClassModelValidator 和DataErrorI nfoPrope均rModelValidator) ,最终是通过具有如下定义的System. Web.Mvc.DataErrorInfoModelValidatorProvider 来提供的。在实现的Get Validators 方法中,如果被验证数据类型实现了IDat aErrorI nfo 接口,它会基于指定的ModelMetadata 和ControllerContext 创建一个DataErrorInfoClassModelValidator 对象置于返回的ModelValidator 集合中。

如果被验证的对象是容器对象的某个属性,并且容器对象的类型(不是属性类型)实现了IDataE rrorInfo 接口,该方法返回的ModelValidator 集合中还会包含一个基于指定Mode lM etadata 和ControllerContext 创建的DataEηorI nfoPrope吗rModelValidator 对象。

private static IEnumerable<ModelValidator> GetValidatorsImpl(ModelMetadata metadata, ControllerContext context)
    {

        // If the metadata describes a model that implements IDataErrorInfo, we should call its

        // Error property at the appropriate time.

        if (TypeImplementsIDataErrorInfo(metadata.ModelType)) {

            yield return new DataErrorInfoClassModelValidator(metadata, context);

        }

        // If the metadata describes a property of a container that implements IDataErrorInfo,

        // we should call its Item indexer at the appropriate time.

        if (TypeImplementsIDataErrorInfo(metadata.ContainerType)) {

            yield return new DataErrorInfoPropertyModelValidator(metadata, context);

        }

    }
  • ModelValidatorProviders

ModelMetadata有一个方法,也可以获取ModelValidator.逻辑也是调用Provider获取的

public virtual IEnumerable<ModelValidator> GetValidators(ControllerContext context)
    {

        return ModelValidatorProviders.Providers.GetValidators(this, context);

    }

    public static class ModelValidatorProviders
    {

        private static readonly ModelValidatorProviderCollection _providers = new ModelValidatorProviderCollection()

{

new DataAnnotationsModelValidatorProvider(),

new DataErrorInfoModelValidatorProvider(),

new ClientDataTypeModelValidatorProvider()

};

        public static ModelValidatorProviderCollection Providers
        {

            get { return _providers; }

        }

    }

    public class ModelValidatorProviderCollection : Collection<ModelValidatorProvider>
    {

        public IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
        {//集合中每个ModelValidatorProvider 创建的ModelValidator 会包含在该列表中。

            return CombinedItems.SelectMany(provider => provider.GetValidators(metadata, context));

        }

    }

技术分享

Model绑定与验证

ModelState

  • Controller中有个ViewDataDictionary类型的ViewData属性
  • ViewDataDictionary类型中有一个ModelStateDictionary的ModelState属性
  • ModelStateDictionary:IDictionary<string, ModelState>
  • public class ModelState
        {
    
            private ModelErrorCollection _errors = new ModelErrorCollection();
    
            //数据验证的结果,错误的集合
    
            public ModelErrorCollection Errors
            {
    
                get
                {
    
                    return this._errors;
    
                }
    
            }
    
            //ValueProvider提供的值
    
            public ValueProviderResult Value { get; set; }
    
        }

验证消息的呈现

  • Model的验证是伴随着Model的绑定完成的.DefaultModelBinder中,在绑定的过程中,有如下的一段代码:
  • if (value == null && bindingContext.ModelState.IsValidField(modelStateKey)) {
    
                ModelValidator requiredValidator = ModelValidatorProviders.Providers.GetValidators(propertyMetadata, controllerContext).Where(v => v.IsRequired).FirstOrDefault();
    
                if (requiredValidator != null) {
    
                    foreach (ModelValidationResult validationResult in requiredValidator.Validate(bindingContext.Model)) {
    
                        bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
    
                    }
    
                }
            }
  • ValidationMessage/ ValidationMessageFor
  • ValidationSummary将所有的验证消息显示一起

ValidationSummary 方法通过判断ModelStateDictionary的Key 是否为空来判断ModelState 是否针对一个属性

Model绑定中的验证

  • Model绑定是递归进行的,验证不是递归的,在Model绑定的过程中会调用验证.
  • DefaultModelBinder中的验证是在这里
  • internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model)
        {
    
            // need to replace the property filter + model object and create an inner binding context
    
            ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model);
    
            // validation
    
            if (OnModelUpdating(controllerContext, newBindingContext)) {
    
                BindProperties(controllerContext, newBindingContext);
    
                OnModelUpdated(controllerContext, newBindingContext);
    
            }
    
        }
    
        protected virtual void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
    
            Dictionary<string, bool> startedValid = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
    
            foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(bindingContext.ModelMetadata, controllerContext).Validate(null)) {
    
                string subPropertyName = CreateSubPropertyName(bindingContext.ModelName, validationResult.MemberName);
    
                if (!startedValid.ContainsKey(subPropertyName)) {
    
                    startedValid[subPropertyName] = bindingContext.ModelState.IsValidField(subPropertyName);
    
                }
    
                if (startedValid[subPropertyName]) {
    
                    bindingContext.ModelState.AddModelError(subPropertyName, validationResult.Message);
    
                }
    
            }
    
        }

基于数据注解特性的Model验证

ValidationAttribute 特性

  • ValidationAttribute 不是MVC中的,是在System.ComponentModel.DataAnnotations中的

继承的时候,需要重写IsValid(,)方法.FormatErrorMessage会格式化错误消息,GetValidationResult会调用IsValid方法得到Result

技术分享

  • ValidationResult包含错误消息和成员名称的组合.一组相关成员的列表

技术分享

  • ValidationContext验证使用的上下文被验证对象的实例,类型,属性名称和显示名称

技术分享

  • ValidationAttribute,只能在一个属性或类上应用一个,如果应用多个,只有一个生效.因为Provider在创建Validator的时候,会按照Attribute的TypeID分组,每个分组选择第一个.

DataAnnotationsModelValidator

  • DataAnnotationsModelValidator对象,在内部调用Attribute属性的验证方法
  • public class DataAnnotationsModelValidator : ModelValidator
        {
    
            public DataAnnotationsModelValidator(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute) { }
    
            public override IEnumerable<ModelValidationResult> Validate(object container)
            {
    
                // Per the WCF RIA Services team, instance can never be null (if you have
    
                // no parent, you pass yourself for the "instance" parameter).
    
                ValidationContext context = new ValidationContext(container ?? Metadata.Model, null, null);
    
                context.DisplayName = Metadata.GetDisplayName();
    
                ValidationResult result = Attribute.GetValidationResult(Metadata.Model, context);
    
                if (result != ValidationResult.Success) {
    
                    yield return new ModelValidationResult {
    
                        Message = result.ErrorMessage
    
                    };
    
                }
    
            }
        }
    DataAnnotationsModelValidator<TAttribute>:DataAnnotationsModelValidator where TAttribute: ValidationAttribute 
        public class DataAnnotationsModelValidator<TAttribute> : DataAnnotationsModelValidator
    
        where TAttribute : ValidationAttribute
        {
    
            public DataAnnotationsModelValidator(ModelMetadata metadata, ControllerContext context, TAttribute attribute)
    
                : base(metadata, context, attribute)
            {
    
            }
    
            protected new TAttribute Attribute
            {
    
                get { return (TAttribute)base.Attribute; }
    
            }
    
        }
        RequiredAttributeAdapter
        public class RequiredAttributeAdapter : DataAnnotationsModelValidator<RequiredAttribute>
            {
        
                public RequiredAttributeAdapter(ModelMetadata metadata, ControllerContext context, RequiredAttribute attribute)
        
                    : base(metadata, context, attribute)
                {
        
                }
        
                public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
                {
        
                    return new[] { new ModelClientValidationRequiredRule(ErrorMessage) };
        
                }
        
            }

                                  DataAnnotationsModelValidatorProvider

                                  • DataAnnotationsModelValidatorProvider

                                  委托DataAnnotationsModelValidationFactory 根据ModelMetadata、ControllerContext 和ValidationAttribute 创建一个ModelValidator 对象。字段AttributeFactories 表示的字典将验证特性的类型作为Key ,换句话说它维护一个ValidationAttribute 特性类型和对应ModelValidator工厂的匹配关系。

                                  在重写的GetValidators 方法中,针对提供的每一个ValidationAttribute 特性,它先根据其类型从AttributeFactories 字典中获取一个对应的DataAnn otationsModelValidationFactory 委托。如果该委托对象存在,则用它来创建相应的ModelValidator 对象,否则就采用字段DefaultAttributeFactory 表示的DataAnnotationsModelValidationFactory 委托来进行ModelValidator 的创建。

                                  public class DataAnnotationsModelValidatorProvider : AssociatedValidatorProvider
                                      {
                                  
                                          internal static DataAnnotationsModelValidationFactory DefaultAttributeFactory =
                                  
                                          (metadata, context, attribute) => new DataAnnotationsModelValidator(metadata, context, attribute);
                                  
                                          internal static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = BuildAttributeFactoriesDictionary();
                                  
                                          private static Dictionary<Type, DataAnnotationsModelValidationFactory> BuildAttributeFactoriesDictionary()
                                          {
                                  
                                              var dict = new Dictionary<Type, DataAnnotationsModelValidationFactory>();
                                  
                                              AddValidationAttributeAdapter(dict, typeof(RangeAttribute),
                                  
                                              (metadata, context, attribute) => new RangeAttributeAdapter(metadata, context, (RangeAttribute)attribute));
                                  
                                              AddValidationAttributeAdapter(dict, typeof(RegularExpressionAttribute),
                                  
                                              (metadata, context, attribute) => new RegularExpressionAttributeAdapter(metadata, context, (RegularExpressionAttribute)attribute));
                                  
                                              AddValidationAttributeAdapter(dict, typeof(RequiredAttribute),
                                  
                                              (metadata, context, attribute) => new RequiredAttributeAdapter(metadata, context, (RequiredAttribute)attribute));
                                  
                                              AddValidationAttributeAdapter(dict, typeof(StringLengthAttribute),
                                  
                                              (metadata, context, attribute) => new StringLengthAttributeAdapter(metadata, context, (StringLengthAttribute)attribute));
                                  
                                              AddValidationAttributeAdapter(dict, ValidationAttributeHelpers.MembershipPasswordAttributeType,
                                  
                                              (metadata, context, attribute) => new MembershipPasswordAttributeAdapter(metadata, context, attribute));
                                  
                                              AddValidationAttributeAdapter(dict, ValidationAttributeHelpers.CompareAttributeType,
                                  
                                              (metadata, context, attribute) => new CompareAttributeAdapter(metadata, context, attribute));
                                  
                                              AddValidationAttributeAdapter(dict, ValidationAttributeHelpers.FileExtensionsAttributeType,
                                  
                                              (metadata, context, attribute) => new FileExtensionsAttributeAdapter(metadata, context, attribute));
                                  
                                              AddDataTypeAttributeAdapter(dict, ValidationAttributeHelpers.CreditCardAttributeType, "creditcard");
                                  
                                              AddDataTypeAttributeAdapter(dict, ValidationAttributeHelpers.EmailAddressAttributeType, "email");
                                  
                                              AddDataTypeAttributeAdapter(dict, ValidationAttributeHelpers.PhoneAttributeType, "phone");
                                  
                                              AddDataTypeAttributeAdapter(dict, ValidationAttributeHelpers.UrlAttributeType, "url");
                                  
                                              return dict;
                                  
                                          }
                                  
                                          private static void AddValidationAttributeAdapter(Dictionary<Type, DataAnnotationsModelValidationFactory> dictionary, Type validataionAttributeType, DataAnnotationsModelValidationFactory factory)
                                          {
                                  
                                              Contract.Assert(dictionary != null);
                                  
                                              if (validataionAttributeType != null) {
                                  
                                                  dictionary.Add(validataionAttributeType, factory);
                                  
                                              }
                                  
                                          }
                                  
                                          private static void AddDataTypeAttributeAdapter(Dictionary<Type, DataAnnotationsModelValidationFactory> dictionary, Type attributeType, string ruleName)
                                          {
                                  
                                              AddValidationAttributeAdapter(
                                  
                                              dictionary,
                                  
                                              attributeType,
                                  
                                              (metadata, context, attribute) => new DataTypeAttributeAdapter(metadata, context, (DataTypeAttribute)attribute, ruleName));
                                  
                                          }
                                  
                                      }

                                  除了AttributeFactories 和DefaultAttributeFactory , DataAnnotationsModelValidatorProvider还具有DefaultValidatableFactory 和ValidatableFactories 两个静态字段,它们用于针对可验证对象(实现了IValidatableObject 接口)的ModelValidator 创建。字段DefaultValidatableFactory的类型是另外一个名为DataAnnotations Validatab leObjectAdapterFactory 的委托,该委托根据

                                  ModelMetadata 和ControllerContext 创建相应的ModelValidator 。字段Validatab leF actories 是一个以此委托为Value 、以Type 对象为Key 的字典。

                                  当DataAnnotationsModelValidatorProvider 完成了针对基于验证特性的ModelValidator 的创建之后,如果被验证数据类型实现了IValidatableObject 接口,它会先从静态字段ValidatableFactories 中根据此类型获取一个对应的DataAnnotationsValidatableObjec tAdapterFactory委托。如果匹配的委托对象存在,则用其进行ModelValidator 的创建,否则采用字段DefaultValidatableFactory 表示的默认工厂来创建相应的ModelValidator 对象。创建的是 public class ValidatableObjectAdapter : ModelValidator.

                                  可以根据需要对Adapter和AdapterFactory进行注册.

                                  技术分享

                                  技术分享

                                  可以通过扩展,使用对参数添加的验证特性.默认的情况下,是不会读取应用在参数上的验证特性的.

                                  DefaultModelBinder对简单参数的绑定,是不会进行参数验证的.只对复杂的参数类型进行验证.可以自定义继承者DefaultModelBinder的类,实现对简单类型的验证.添加对简单类型验证的代码

                                  ParameterDescription包含了应用在参数上的特性

                                  默认的数据验证Provider,是根据参数的数据类型生成ModelValidator的,没有读取参数上的特性.因此可以自定义继承自DataAnnotationsModelValidatorProvider的Provider,读取参数上的特性,创建ModelValidator,添加到默认的列表中.

                                  想办法将ParameterDescription的信息传递到Provider中,因此可以将信息放入ControllerContext中传递过去.自定义ActionInvoke,将信息放入ControllerContext中

                                  略…

                                  一种Model类型,多种验证规则

                                  1. 定义一种应用在数据上的可使用多个的验证特性.每个特性有一个名称标示.重写TypeId属性,返回不同的值,这样Provider会根据每一个特性创建一个验证,否则会默认取相同类型的第一个特性.
                                  1. 定义一种应用在Controller或者Action上的特性,给这个特性添加一个参数,表示要使用的验证特性(第一步中定义的)的名称.
                                  1. 在某一个环节中,将第二部中定义的特性,也就是要使用的验证特性的名称添加到ControllerContext中,如DataTokens中.可以再ActionInvoke中或者ExecuteCore中实现这个操作.
                                  1. 定义一个Provider继承DataAnnotationsModelValidatorProvider,读取特性列表,读取DataToken中的名称,筛选出要使用的相同类型特性中的某一个.添加到原有的Validator集合中.并将此Provider替换原有的Provider

                                  客户端验证

                                  JQuery验证

                                  1. 以内敛的方式制定验证规则,将验证规则名称放在Html的Class属性中如<input Class="Required URL"…>
                                  1. 单独制定验证规则和错误消息,通过JS根据属性名称指定所使用的验证规则和错误消息.

                                  技术分享

                                  基于JQuery的Model验证

                                  Data-val表示对用户的输入值验证

                                  Data-val-name name表示JQuery验证规则 的名称

                                  Data-val-name-para表示可选的验证参数

                                  ModelClientValidationRule表示客户端验证规则

                                  ModelValidator中有一个方法GetClientValidationRules返回客户端验证规则

                                  客户端验证在这里还涉及到一个重要的接口System. Web.Mvc.IClientValidatable ,它具有唯一的GetClientValidationRules 方法来返回一个以System.Web.Mvc.ModelClientValidationRule对象表示的客户端验证规则列表。所有支持客户端验证的ValidationAttribute 都需要实现IClientValidatable 接口并通过实现.DataAnnotationsModelValidator.GetClientValidationRules 方法,如果对应的ValidationA伽ibute 实现了IClientValidatable 接口,它( ValidationAttribute )的GetClientValidationRules 方法被调用并将返回的ModelClientValidationRule 列表作为该方法的返回值。

                                  Model的验证

                                  标签:

                                  原文地址:http://www.cnblogs.com/zhangliming/p/4567270.html

                                  (0)
                                  (0)
                                     
                                  举报
                                  评论 一句话评论(0
                                  登录后才能评论!
                                  © 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
                                  迷上了代码!