使用 MVC 4 在运行时动态应用验证规则 ASP.NET
本文关键字:验证 规则 ASP NET 应用 动态 MVC 运行时 使用 | 更新日期: 2023-09-27 18:32:13
我已经在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 集合,并在运行时应用每个 Set 实例中包含的验证规则,以实现我在编译时在模型上使用 DataAnnotations 获得的相同的客户端和服务器端验证。在WebForms中,我只是将适当的验证器附加到相关字段,但是我在MVC4中找到类似的机制时遇到了麻烦。有没有办法实现这一目标?
我的解决方案是扩展 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 = ""};
}
}
}
}
上面的代码将在模型上查找要用作验证开关的属性(默认为"必需")。如果要用作开关的布尔属性设置为 true,则客户端和服务器端验证都对用 RuntimeRequiredValdiationAttribute
修饰的属性执行。请务必注意,此类假定用于验证开关的模型的任何属性都不会显示给最终用户进行编辑,即这不是 RequiredIf 验证器。
实际上还有另一种方法可以实现验证属性以及客户端验证,如此处所述。为了进行比较,我上面所做的IClientValidatable路由由同一作者在这里概述。
请注意,这目前不适用于嵌套对象,例如,如果该属性装饰另一个对象包含的对象的属性,它将不起作用。有一些选择可以解决这个缺点,但到目前为止,对我来说还没有必要。
使用RemoteAttribute。这应该对服务器执行不显眼的 ajax 调用以验证数据。
正如我在上面的评论中所说,我已经使用反射做了类似的事情。您可以忽略其中的一些,例如,您可能不需要字典,因为这只是为他们提供自定义可翻译消息的一种方式。
服务器端代码:
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
}
}
如果这些不是很清楚,请道歉。随时提出任何问题,我会尽力解释。
我已经很长时间没有使用 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;}
}