我正在实现一个自定义验证属性。此属性不仅查看它所应用到的属性的值,还查看另一个属性的值。另一个属性由其名称指定。
我需要找到一种方法来获取其他属性的输入将在最终 HTML 输出中具有的完整 id。
这是我的验证属性的简化版本:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class MyCustomValidationAttribute : ValidationAttribute, IClientModelValidator
{
private string _otherPropertyName;
public MyCustomValidationAttribute(string otherPropertyName)
{
_otherPropertyName = otherPropertyName;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var otherProperty = context.ObjectInstance.GetType().GetProperty(_otherPropertyName);
var otherPropertyValue = Convert.ToString(otherProperty.GetValue(context.ObjectInstance, null));
// Validation logic...
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val-mycustomvalidation", errorMessage);
// THIS ROW NEEDS TO BE FIXED
MergeAttribute(context.Attributes, "data-val-mycustomvalidation-otherpropertyname", _otherProperyName);
}
private void MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (!attributes.ContainsKey(key))
{
attributes.Add(key, value);
}
}
}
这演示了如何在模型类中使用它:
public class Report
{
[MyCustomValidation("Value2", ErrorMessage = "Error...")]
public string Value1 { get; set; }
public string Value2 { get; set; }
}
这是确保验证也在客户端完成的 JavaScript:
$.validator.addMethod('mycustomvalidation',
function (value, element, parameters) {
var otherPropertyValue = $('#' + parameters.otherpropertyname).val();
// Validation logic...
});
$.validator.unobtrusive.adapters.add('mycustomvalidation', ['otherpropertyname'],
function (options) {
options.rules.mycustomvalidation = options.params;
options.messages['mycustomvalidation'] = options.message;
});
带有表单的页面/视图的视图模型如下所示:
public MyViewModel
{
public Report MyReport { get; set; }
}
请注意,我不会将报表用作视图模型,而是用作视图模型中的属性类型。这很重要,因为这是我问题的根源......
视图中显示 Value1 输入的代码并不奇怪(我使用的是 Razor 页面):
<div>
<label asp-for="MyReport.Value1"></label>
<input asp-for="MyReport.Value1" />
<span asp-validation-for="MyReport.Value1"></span>
</div>
输出变为:
<label for="MyReport_Value1">Value1</label>
<input
type="text"
id="MyReport_Value1"
name="MyReport.Value1"
data-val="true"
data-val-mycustomvalidation="Error..."
data-val-mycustomvalidation-otherpropertyname="Value2"
value=""
>
<span
data-valmsg-for="MyReport.Value1"
data-valmsg-replace="true"
class="text-danger field-validation-valid"
></span>
所以问题是在HTML输出中,我需要data-val-mycustomvalidation-otherpropertyname是"MyReport_Value2"而不仅仅是"Value2"。否则,验证代码将无法找到第二个 HTML 输入(ID 为 MyReport_Value2)并执行验证。
我认为这必须在属性类中的 AddValidation() 方法中完成,但是如何获取 HTML 输入将收到的全名?
我猜有一些方法可以使用上下文参数来获取它。我见过类似"*"的例子。TemplateInfo.GetFullHtmlFieldId(PropertyName)",但我无法让它工作。
任何帮助不胜感激!
您将Value2
传递给MyCustomValidationAttribute
并使用Value2
设置_otherPropertyName
,并使用
MergeAttribute(context.Attributes, "data-val-mycustomvalidation-otherpropertyname", _otherProperyName);
这样 HTML 将是
data-val-mycustomvalidation-otherpropertyname="Value2"
您只需要将Report_Value2
传递给MyCustomValidationAttribute
而不是Value2
。
public class Report
{
[MyCustomValidation("Report_Value2", ErrorMessage = "Error...")]
public string Value1 { get; set; }
public string Value2 { get; set; }
}
这样你就会得到data-val-mycustomvalidation-otherpropertyname="Report_Value2"
ValidationContext绑定到属于验证属性的实例,即Model。因此,查找视图模型的引用看起来很困难。 我可以提供三种不同的解决方案,您可以使用哪种解决方案适合您的要求。
解决方案 1:
使用 ValidationContext,您可以获取属性所属类的名称。仅当视图模型属性名称必须与模型类名称相同时,此解决方案才有效。 例如,如果模型类是学生,则属性名称必须是学生。如果属性名称为 Student1,则它不起作用。 即使类名和属性名不同,解决方案 2 和 3 也将起作用。
型
public class Student
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage = "Please enter name")]
public string Name { get; set; }
[Required]
[Country("Name")]
public string Country { get; set; }
}
视图模型
public class StudentViewModel
{
public Student Student {get;set;} //Solution 1 wil not work for Student1
}
验证属性
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class CountryAttribute : ValidationAttribute, IClientModelValidator
{
private string _otherPropertyName;
private string _clientPropertyName;
public CountryAttribute(string otherPropertyName)
{
_otherPropertyName = otherPropertyName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var otherProperty = validationContext.ObjectInstance.GetType().GetProperty(_otherPropertyName);
var otherPropertyValue = Convert.ToString(otherProperty.GetValue(validationContext.ObjectInstance, null));
_clientPropertyName = otherProperty.DeclaringType.Name +"_"+ otherProperty.Name;
}
public void AddValidation(ClientModelValidationContext context)
{
context.Attributes.Add("data-val", "true");
context.Attributes.Add("data-val-mycustomvalidation-otherpropertyname", _clientPropertyName);
}
}
解决方案 2:
使用 ClientModelValidationContext,您可以获取从控制器传递到视图的 ViewModel 引用。通过使用反射,我们可以获取属性的名称,即模型。 要使用解决方案,您需要从控制器传递空的 ViewModel 引用。
控制器
public IActionResult New()
{
StudentViewModel studentViewModel = new StudentViewModel();
return View(studentViewModel);
}
验证属性
public void AddValidation(ClientModelValidationContext context)
{
var otherClientPropName = context.ModelMetadata.ContainerMetadata.Properties
.Single(p => p.PropertyName == this._otherPropertyName)
.GetDisplayName();
var viewContext = context.ActionContext as ViewContext;
if (viewContext?.ViewData.Model is StudentViewModel)
{
var model = (StudentViewModel)viewContext?.ViewData.Model;
var instanceName = model.GetType().GetProperties()[0].Name;
otherClientPropName = instanceName + "_" + otherClientPropName;
}
context.Attributes.Add("data-val", "true");
context.Attributes.Add("data-val-mycustomvalidation-otherpropertyname", otherClientPropName);
}
解决方案 3:
使用上下文。属性["id"] 您可以获取当前属性 id 值作为字符串。通过使用字符串操作,您可以获取前缀,然后可以与其他属性名称合并。 此解决方案不需要来自控制器的空视图模型引用。
控制器
public IActionResult New()
{
return View();
}
验证属性
public void AddValidation(ClientModelValidationContext context)
{
var otherClientPropName = context.ModelMetadata.ContainerMetadata.Properties
.Single(p => p.PropertyName == this._otherPropertyName)
.GetDisplayName();
var id = context.Attributes["id"];
var idPrefix = id.Split("_");
if (idPrefix.Length > 1)
{
otherClientPropName = idPrefix[0] + "_" + otherClientPropName;
}
context.Attributes.Add("data-val", "true");
context.Attributes.Add("data-val-mycustomvalidation-otherpropertyname", otherClientPropName);
}
网页输出
<input class="form-control" type="text" data-val="true" data-val-required="Please enter name" id="Student_Name" name="Student.Name" value="">
<input class="form-control input-validation-error" type="text" data-val="true" data-val-mycustomvalidation-otherpropertyname="Student_Name" data-val-required="The Country field is required." id="Student_Country" name="Student.Country" value="">
当呈现的字段是模型的更深的子级时,此方法也有效。
//Build the client id of the property name.
var dependentClientId = dependentPropertyName;
var clientId = context.Attributes["id"];
var clientIdArr = clientId.Split("_");
if (clientIdArr.Length > 1)
{
//Replace the last value of the array with the dependent property name.
clientIdArr[clientIdArr.Length - 1] = dependentPropertyName;
dependentClientId = string.Join("_", clientIdArr);
}
MergeAttribute(context.Attributes, "data-val-mycustomvalidation-otherpropertyname", dependentClientId );