【问题标题】:Dynamically apply validation rules at runtime with ASP.NET MVC 4使用 ASP.NET MVC 4 在运行时动态应用验证规则
【发布时间】:2014-06-28 00:39:53
【问题描述】:

我多年来一直在 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 中找不到类似的机制。有没有办法做到这一点?

【问题讨论】:

  • 前几天我用反射写了一些类似的东西。我们有一个表格,不同的人可以指定不同的必填字段。你追求的是那种东西吗?如果是这样,我可以粘贴代码作为答案。
  • @GazWinter 听起来好像是在正确的轨道上。
  • 根据我发送的代码你有什么运气吗?
  • @GazWinter 我实际上是在扩展 ValidationAttribute 类以达到预期的效果。我很快就会发布一个完整的代码示例。

标签: c# validation asp.net-mvc-4


【解决方案1】:

我的解决方案是扩展 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 验证器。

除了客户端验证as outlined here,实际上还有另一种实现ValidationAttribute 的方法。作为比较,我上面所做的 IClientValidatable 路由是outlined by the same author here

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

【讨论】:

  • 我从这里被提到你的答案:stackoverflow.com/questions/20401389/…。我明白你在这里做了什么。我过去做过类似的事情,但你有没有动态地做它?为什么元数据参数为空?
  • 注意,在代码示例中,context.Controller.ViewData.Model是主视图的模型,这意味着如果你想制作这个是行不通的签入具有不同模型的子局部视图。
  • @NikolayArhangelov 你也许可以让它工作。尝试将 context.Controller.ViewData.Model 切换到 metadata.Container。我不确定其中的含义,但它似乎有效。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    服务器端代码

     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&lt;RequiredField&gt;,它基本上是一个包含两个字符串的类,一个用于字段名称,一个用于错误消息。

    一旦你将模型传递到视图中,如果你想检查服务器端,你需要一些 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
    
                     }
    
              }
    

    如果其中任何内容不是很清楚,我们深表歉意。如有任何问题,请随时提出,我会尽力解释。

    【讨论】:

      【解决方案4】:

      我很长时间没有使用 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;}
      }
      

      【讨论】:

      • 如果你重新阅读这个问题,你会发现这不是我想要的。这会在编译时将验证规则分配给模型。我需要在运行时分配规则。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-17
      • 1970-01-01
      • 2013-05-23
      相关资源
      最近更新 更多