C# 使用 ASP.NET MVC 4 在运行时动态应用验证规则
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18919758/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Dynamically apply validation rules at runtime with ASP.NET MVC 4
提问by joelmdev
I've been working in WebForms for years but I'm fairly new to .NET's flavor of MVC. I am trying to figure out how to apply dynamic validation rules to members of my model at runtime. For purposes of this question these are simplified versions of the classes I'm working with:
我多年来一直在 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;}
}
In my view I would iterate through the Settings collection with editors for each and apply the validation rules contained in each Setting instance at runtime to achieve the same client and server-side validation that that I get from using DataAnnotations on my model at compile-time. In WebForms I would have just attached the appropriate Validator to the associated field but I'm having trouble finding a similar mechanism in MVC4. Is there a way to achieve this?
在我看来,我将使用编辑器遍历 Settings 集合,并在运行时应用每个 Setting 实例中包含的验证规则,以实现与我在编译时在模型上使用 DataAnnotations 所获得的相同的客户端和服务器端验证. 在 WebForms 中,我只是将适当的 Validator 附加到相关字段,但我在 MVC4 中找不到类似的机制。有没有办法实现这一目标?
采纳答案by joelmdev
My solution was to extend the ValidationAttribute class and implement the IClientValidatable interface. Below is a complete example with some room for improvement:
我的解决方案是扩展 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 = ""};
}
}
}
}
The code above will look for a property on the model to use as a switch for the validation (IsRequired is default). If the boolean property to be used as a switch is set to true, then both client and server-side validation are performed on the property decorated with the RuntimeRequiredValdiationAttribute
. It's important to note that this class assumes that whatever property of the model is being used for the validation switch will not be displayed to the end user for editing, i.e. this is not a RequiredIf validator.
上面的代码将在模型上查找一个属性以用作验证的开关(IsRequired 是默认值)。如果要用作开关的布尔属性设置为 true,则客户端和服务器端验证都会对用RuntimeRequiredValdiationAttribute
. 需要注意的是,这个类假定模型的任何属性用于验证开关都不会显示给最终用户进行编辑,即这不是一个 RequiredIf 验证器。
There is actually another way to implement a ValidationAttribute along with client-side validation as outlined here. For comparison, the IClientValidatable route as I have done above is outlined by the same author here.
实际上还有另一种方法来实现 ValidationAttribute 以及此处概述的客户端验证。为了比较,我上面所做的 IClientValidatable 路由是由同一作者在此处概述的。
Please notethat this doesn't currently work with nested objects, eg if the attribute decorates a property on an object contained by another object, it won't work. There are some options for solving this shortcoming, but thus far it hasn't been necessary for me.
请注意,这当前不适用于嵌套对象,例如,如果该属性修饰了另一个对象包含的对象上的属性,则它将不起作用。有一些选项可以解决这个缺点,但到目前为止对我来说还没有必要。
回答by Adn12
I havent been working with MVC4 for a long time, so forgive me if i am wrong, but you can server side and client side validation using jquery-val (already available to you if you used the "internet application" template when creating your project) and attributes:
我已经很长时间没有使用 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;}
}
回答by neeKo
You could use RemoteAttribute. This should perform unobtrusive ajax call to the server to validate your data.
您可以使用RemoteAttribute。这应该对服务器执行不显眼的 ajax 调用以验证您的数据。
回答by Gaz Winter
As i said in my comment above i have done something similar using reflection. You can ignore some of it, you probably don't need the dictionary for example, as that was just a way of giving them custom translatable messages.
正如我在上面的评论中所说,我使用反射做了类似的事情。您可以忽略其中的一些内容,例如,您可能不需要字典,因为这只是向他们提供自定义可翻译消息的一种方式。
Server side code:
服务器端代码:
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);
}
}
On the model we have a List<RequiredField>
which is basically a class with two strings, one for the field name and one for the error message.
在模型上,我们有一个List<RequiredField>
基本上是一个包含两个字符串的类,一个用于字段名称,另一个用于错误消息。
Once you have passed the model into the view you need a bit of jQuery to add the validation stuff to the page if you want to do the check server side.
一旦你将模型传递到视图中,如果你想在服务器端进行检查,你需要一些 jQuery 来向页面添加验证内容。
Client side code:
客户端代码:
$("#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
}
}
Apologies if any of this is not very clear. Feel free to ask any questions and I will do my best to explain.
如果其中任何一个不是很清楚,请道歉。随时提出任何问题,我会尽力解释。