8

我多年来一直在 WebForms 中工作,但我对 .NET 的 MVC 风格还很陌生。我试图弄清楚如何在运行时将动态验证规则应用于我的模型成员。出于这个问题的目的,这些是我正在使用的类的简化版本:

public class Device
{
   public int Id {get; set;}
   public ICollection<Setting> Settings {get; set;}
}

public class Setting
{
   public int Id {get; set;} 
   public string Value {get; set;}
   public bool IsRequired {get; set;}
   public int MinLength {get; set;}
   public int MaxLength {get; set;}
}

在我看来,我将使用每个编辑器遍历 Settings 集合,并在运行时应用每个 Setting 实例中包含的验证规则,以实现与在编译时在模型上使用 DataAnnotations 获得的相同的客户端和服务器端验证. 在 WebForms 中,我只是将适当的 Validator 附加到相关字段,但我在 MVC4 中找不到类似的机制。有没有办法做到这一点?

4

4 回答 4

9

我的解决方案是扩展 ValidationAttribute 类并实现 IClientValidatable 接口。下面是一个完整的例子,有一些改进的余地:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Web.Mvc;

namespace WebApplication.Common
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
    public class RuntimeRequiredAttribute : ValidationAttribute, IClientValidatable
    {
        public string BooleanSwitch { get; private set; }
        public bool AllowEmptyStrings { get; private set; }

        public RuntimeRequiredAttribute(string booleanSwitch = "IsRequired", bool allowEmpytStrings = false ) : base("The {0} field is required.")
        {
            BooleanSwitch = booleanSwitch;
            AllowEmptyStrings = allowEmpytStrings;
        }

            protected override ValidationResult IsValid(object value, ValidationContext validationContext)
            {
                PropertyInfo property = validationContext.ObjectType.GetProperty(BooleanSwitch);

                if (property == null || property.PropertyType != typeof(bool))
                {
                    throw new ArgumentException(
                        BooleanSwitch + " is not a valid boolean property for " + validationContext.ObjectType.Name,
                        BooleanSwitch);
                }

                if ((bool) property.GetValue(validationContext.ObjectInstance, null) &&
                    (value == null || (!AllowEmptyStrings && value is string && String.IsNullOrWhiteSpace(value as string))))
                {
                    return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
                }

                return ValidationResult.Success;
            }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
            ControllerContext context)
        {
            object model = context.Controller.ViewData.Model;
            bool required = (bool)model.GetType().GetProperty(BooleanSwitch).GetValue(model, null);

            if (required)
            {
                yield return
                    new ModelClientValidationRequiredRule(
                        FormatErrorMessage(metadata.DisplayName ?? metadata.PropertyName));
            }
            else
            //we have to return a ModelCLientValidationRule where
            //ValidationType is not empty or else we get an exception
            //since we don't add validation rules clientside for 'notrequired'
            //no validation occurs and this works, though it's a bit of a hack
            {
                yield return
                    new ModelClientValidationRule {ValidationType = "notrequired", ErrorMessage = ""};
            }
        }
    }
}

上面的代码将在模型上查找一个属性以用作验证的开关(默认为 IsRequired)。如果要用作开关的布尔属性设置为 true,则客户端和服务器端验证都会对使用RuntimeRequiredValdiationAttribute. 重要的是要注意,这个类假定用于验证开关的模型的任何属性都不会显示给最终用户进行编辑,即这不是RequiredIf 验证器。

实际上还有另一种实现 ValidationAttribute 以及此处概述的客户端验证的方法。作为比较,我在上面所做的 IClientValidatable 路线是由同一作者在这里概述的

请注意,这目前不适用于嵌套对象,例如,如果该属性装饰了另一个对象包含的对象上的属性,它将不起作用。有一些解决这个缺点的选择,但到目前为止,对我来说还没有必要。

于 2013-09-23T17:41:26.100 回答
3

您可以使用RemoteAttribute。这应该对服务器执行不显眼的 ajax 调用以验证您的数据。

于 2013-09-20T15:16:27.840 回答
1

正如我在上面的评论中所说,我使用反射做了类似的事情。您可以忽略其中的一些,例如您可能不需要字典,因为这只是为他们提供自定义可翻译消息的一种方式。

服务器端代码

 private static Dictionary<string, ILocalisationToken> _requiredValidationDictionary;

 private static Dictionary<string, ILocalisationToken> RequiredValidationDictionary(UserBase model)
 {
      if (_requiredValidationDictionary != null)
          return _requiredValidationDictionary;

      _requiredValidationDictionary = new Dictionary<string, ILocalisationToken>
      {
             { model.GetPropertyName(m => m.Publication), ErrorMessageToken.PublicationRequired},
             { model.GetPropertyName(m => m.Company), ErrorMessageToken.CompanyRequired},
             { model.GetPropertyName(m => m.JobTitle), ErrorMessageToken.JobTitleRequired},
             { model.GetPropertyName(m => m.KnownAs), ErrorMessageToken.KnownAsRequired},
             { model.GetPropertyName(m => m.TelephoneNumber), ErrorMessageToken.TelephoneNoRequired},
             { model.GetPropertyName(m => m.Address), ErrorMessageToken.AddressRequired},
             { model.GetPropertyName(m => m.PostCode), ErrorMessageToken.PostCodeRequired},
             { model.GetPropertyName(m => m.Country), ErrorMessageToken.CountryRequired}
      };
      return _requiredValidationDictionary;

  }

  internal static void SetCustomRequiredFields(List<string> requiredFields, UserBase model, ITranslationEngine translationEngine)
  {
      if (requiredFields == null || requiredFields.Count <= 0) return;
      var tokenDictionary = RequiredValidationDictionary(model);
      //Loop through requiredFields and add Display text dependant on which field it is.
  foreach (var requiredField in requiredFields.Select(x => x.Trim()))
  {
      ILocalisationToken token;

      if (!tokenDictionary.TryGetValue(requiredField, out token))
         token = LocalisationToken.GetFromString(string.Format("{0} required", requiredField));

      //add to the model.
      model.RequiredFields.Add(new RequiredField
      {
         FieldName = requiredField,
         ValidationMessage = translationEngine.ByToken(token)
      });
      }
  }

  internal static void CheckForRequiredField<T>(ModelStateDictionary modelState, T fieldValue, string fieldName,                                                            IList<string> requiredFields,                                                          Dictionary<string, ILocalisationToken> tokenDictionary)
   {
        ILocalisationToken token;
        if (!tokenDictionary.TryGetValue(fieldName, out token))
           token = LocalisationToken.GetFromString(string.Format("{0} required", fieldName));
        if (requiredFields.Contains(fieldName) && (Equals(fieldValue, default(T)) || string.IsNullOrEmpty(fieldValue.ToString())))
             modelState.AddModelError(fieldName, token.Translate());
   }

  internal static void CheckForModelErrorForCustomRequiredFields(UserBase model,                                                                             Paladin3DataAccessLayer client, ICache cache,                                                                             ModelStateDictionary modelState)
  {

      var requiredFields = Common.CommaSeparatedStringToList                          (client.GetSettingValue(Constants.SettingNames.RequiredRegistrationFields, cache: cache, defaultValue: String.Empty, region: null)).Select(x => x.Trim()).ToList();
      var tokenDictionary = RequiredValidationDictionary(model);

      foreach (var property in typeof(UserBase)             .GetProperties(BindingFlags.Instance |                                               BindingFlags.NonPublic |                                               BindingFlags.Public))
      {
            CheckForRequiredField(modelState, property.GetValue(model, null), property.Name, requiredFields, tokenDictionary);
      }
  }

在模型上,我们有一个List<RequiredField>基本上是一个包含两个字符串的类,一个用于字段名称,一个用于错误消息。

一旦你将模型传递到视图中,如果你想检查服务器端,你需要一些 jQuery 来将验证内容添加到页面中。

客户端代码

   $("#YOURFORM").validate();
        for (var x = 0; x < requiredFields.length; x++) {
            var $field = $('#' + requiredFields[x].FieldName.trim());

            if ($field.length > 0) {
                $field.rules("add", {
                      required: true,
                      messages: {
                           required: "" + requiredFields[x].ValidationMessage  
                           //required: "Required Input"
                      }
                });

            $field.parent().addClass("formRequired"); //Add a class so that the user knows its a required field before they submit

                 }

          }

抱歉,如果其中任何一个不是很清楚。随时提出任何问题,我会尽力解释。

于 2013-09-20T22:42:37.217 回答
0

我已经很久没有使用 MVC4 了,如果我错了,请原谅我,但是您可以使用 jquery-val 进行服务器端和客户端验证(如果您在创建项目时使用了“互联网应用程序”模板,您已经可以使用) ) 和属性:

public class Device
{
    public int Id {get; set;}
    public ICollection<Setting> Settings {get; set;}
}

public class Setting
{
    [Required]
    public int Id {get; set;} 
    [Range(1,10)]
    public string Value {get; set;}
    [Required]
    public bool IsRequired {get; set;}
    public int MinLength {get; set;}
    public int MaxLength {get; set;}
}
于 2013-09-20T15:05:32.343 回答