英文:
ASP.NET Core 6 : custom vallidation attribute with client-side validation
问题
以下是代码的翻译部分:
我有一些表单,我不得不为其创建自定义验证属性。我的自定义属性在服务器端工作,但我不希望它去服务器端验证。我希望它能在客户端工作。
这个验证器的作用是查看已选择的属性。如果属性的值已选择,那么此属性就是必需的。
该属性的应用方式如下:
[AnotherTest("CurrentlyPracticing", 1,
ErrorMessage = "Required if practicing.")]
public string? ProfLicense { get; set; } = string.Empty;
如你所见,只有当 CurrentlyPracticing = 1
时,ProfLicense
属性才是必需的。
这是我的模型类、自定义验证属性代码和此项目的视图。
我的模型类(请忽略已注释部分 - 它还在进行中):
public class ApplicationVM
{
// 其他属性因 Stackoverflow 空间要求而被移除
[Required(ErrorMessage = "You must answer this question.")]
[Display(Name = "Are you a licensed medical practitioner?")]
public int? LicMedPract { get; set; }
[RequiredIfTrue(nameof(LicMedPract), ErrorMessage = "An answer is required.")]
[Display(Name = "Currently Practicing?")]
public int? CurrentlyPracticing { get; set; }
[RequiredIfTrue(nameof(CurrentlyPracticing), ErrorMessage = "You must select a Specialty.")]
[Display(Name = "Practice Specialty:")]
public int? SpecialtyID { get; set; }
[RequiredIfTrue(nameof(SpecialtyID), ErrorMessage = "You must type an Other Specialty.")]
[StringLength(25)]
[Display(Name = "Other Specialty:")]
public string PracSpecOther { get; set; } = string.Empty;
[Display(Name = "License Number")]
[AnotherTest("CurrentlyPracticing", 1,
ErrorMessage = "Required if practicing.")]
public string? ProfLicense { get; set; } = string.Empty;
[MaxLength(2)]
[Display(Name = "State Licensed")]
[AnotherTest("CurrentlyPracticing", 1,
ErrorMessage = "Required if practicing.")]
public string? ProfLicenseState { get; set; } = string.Empty;
}
这是我的自定义验证属性:
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System.ComponentModel.DataAnnotations;
namespace pportal2.common.Validation
{
public class AnotherTestAttribute : ValidationAttribute, IClientModelValidator
{
private readonly string _propertyName;
private readonly object _desiredValue;
private readonly RequiredAttribute _required;
public AnotherTestAttribute(string propertyName, Object desiredValue)
{
_propertyName = propertyName;
_desiredValue = desiredValue;
_required = new RequiredAttribute();
}
public void AddValidation(ClientModelValidationContext context)
{
var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-testif", errorMessage);
MergeAttribute(context.Attributes, "data-val-testif-propertyname", _propertyName);
MergeAttribute(context.Attributes, "data-val-testif-desiredvalue", _desiredValue.ToString());
}
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
var dependentValue = validationContext.ObjectInstance.GetType().GetProperty(_propertyName).GetValue(validationContext.ObjectInstance, null);
if (dependentValue.ToString() == _desiredValue.ToString())
{
if (!_required.IsValid(value))
{
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
}
这是我的视图(这里我认为问题出在验证器部分。这在脚本的底部):
<!-- 以下为视图的 HTML 代码,已略过具体内容 -->
希望这能帮助你理解代码的翻译。如果有其他问题,请随时提出。
英文:
I have forms that I have had to create custom validation attributes for. My custom attribute works at the server, but I do not want it to go to the server to validate this. I would love for it to work at the client-side.
What this validator does is looks to see what has been selected for a stated property. If the value of the property is selected, then this property is required.
The attribute is applied like this:
[AnotherTest("CurrentlyPracticing", 1,
ErrorMessage = "Required if practicing.")]
public string? ProfLicense { get; set; } = string.Empty;
As you can see, the ProfLicense
property is only required if CurrentlyPracticing = 1
.
Here is my model class, custom validation attribute code, and view for this project.
My model class (ignore the commented out portions - it is a work in progress):
public class ApplicationVM
{
// other attributes removed because of Stackoverflow space requirements
[Required(ErrorMessage = "You must answer this question.")]
[Display(Name = "Are you a licensed medical practitioner?")]
public int? LicMedPract { get; set; }
[RequiredIfTrue(nameof(LicMedPract), ErrorMessage = "An answer is required.")]
[Display(Name = "Currently Practicing?")]
public int? CurrentlyPracticing { get; set; }
[RequiredIfTrue(nameof(CurrentlyPracticing), ErrorMessage = "You must select a Specialty.")]
[Display(Name = "Practice Specialty:")]
public int? SpecialtyID { get; set; }
[RequiredIfTrue(nameof(SpecialtyID), ErrorMessage = "You must type an Other Specialty.")]
[StringLength(25)]
[Display(Name = "Other Specialty:")]
public string PracSpecOther { get; set; } = string.Empty;
[Display(Name = "License Number")]
[AnotherTest("CurrentlyPracticing", 1,
ErrorMessage = "Required if practicing.")]
public string? ProfLicense { get; set; } = string.Empty;
[MaxLength(2)]
[Display(Name = "State Licensed")]
[AnotherTest("CurrentlyPracticing", 1,
ErrorMessage = "Required if practicing.")]
public string? ProfLicenseState { get; set; } = string.Empty;
}
Here is my custom validation attribute:
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System.ComponentModel.DataAnnotations;
namespace pportal2.common.Validation
{
public class AnotherTestAttribute : ValidationAttribute, IClientModelValidator
{
private readonly string _propertyName;
private readonly object _desiredValue;
private readonly RequiredAttribute _required;
public AnotherTestAttribute(string propertyName, Object desiredValue)
{
_propertyName = propertyName;
_desiredValue = desiredValue;
_required = new RequiredAttribute();
}
public void AddValidation(ClientModelValidationContext context)
{
var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-testif", errorMessage);
MergeAttribute(context.Attributes, "data-val-testif-propertyname", _propertyName);
MergeAttribute(context.Attributes, "data-val-testif-desiredvalue", _desiredValue.ToString());
}
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
var dependentValue = validationContext.ObjectInstance.GetType().GetProperty(_propertyName).GetValue(validationContext.ObjectInstance, null);
if (dependentValue.ToString() == _desiredValue.ToString())
{
if (!_required.IsValid(value))
{
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
}
My view (here I think my issue is at the validator section. This is at the bottom of the scripts):
<div class="col-md-8 m-auto">
<h1 class="text-center">PACE Program Initial Application</h1>
</div>
<hr />
<div class="col-md-8 m-auto">
<form asp-action="Application" id="application">
<hr />
</div>
<div class="form-group">
<h4>
<label>Professional Information:</label>
</h4>
<div class="form-group col-sm-12">
<div class="row">
<div class="form-group col-sm-6">
<label asp-for="LicMedPract" class="control-label"></label>
<select asp-for="LicMedPract" class="form-control" asp-items="ViewBag.YesNo2"></select>
<span asp-validation-for="LicMedPract" class="text-danger"></span>
</div>
</div>
<div id="divHiddenPracticeInfo" style="display:none;">
<div class="row">
<div class="form-group col-sm-5 col-md-4 col-lg-3">
<label asp-for="CurrentlyPracticing" class="control-label"></label>
<select asp-for="CurrentlyPracticing" class="form-control" asp-items="ViewBag.YesNo2"></select>
<span asp-validation-for="CurrentlyPracticing" class="text-danger"></span>
</div>
<div class=" col-sm-5 col-md-4 col-lg-3" id="divPracticeSpecialty" style="display: none;">
<div class="form-group col-md-12">
<label asp-for="SpecialtyID" class="control-label"></label>
<select asp-for="SpecialtyID" class="form-control" asp-items="ViewBag.Specialty"></select>
<span asp-validation-for="SpecialtyID" class="text-danger"></span>
</div>
</div>
<div class=" col-sm-5 col-md-4 col-lg-3" id="divPracSpecOther" style="display: none;">
<div class="form-group col-md-12">
<label asp-for="PracSpecOther" class="control-label"></label>
<input asp-for="PracSpecOther" class="form-control" />
<span asp-validation-for="PracSpecOther" class="text-danger"></span>
</div>
</div>
</div>
<div class="row">
<h5 class="col-md-12">Please include all letters and numbers.</h5>
<div class="form-group col-sm-5 col-md-4 col-lg-3">
<label asp-for="ProfLicense" class="control-label"></label>
<input asp-for="ProfLicense" class="form-control" />
<span asp-validation-for="ProfLicense" class="text-danger"></span>
</div>
<div class="form-group col-sm-5 col-md-4 col-lg-3">
<label asp-for="ProfLicenseState" class="control-label"></label>
<input asp-for="ProfLicenseState" class="form-control" />
<span asp-validation-for="ProfLicenseState" class="text-danger"></span>
</div>
</div>
<div class="row">
<div class="form-group col-sm-5 col-md-4 col-lg-3">
<label asp-for="DEA" class="control-label"></label>
<input asp-for="DEA" class="form-control" />
<span asp-validation-for="DEA" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
<div asp-validation-summary="All" class="text-danger">
<span id="message2"></span>
</div>
<div class="form-group">
<input type="submit" value="Submit" class="btn btn-primary" />
</div>
<br />
</div>
</form>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
<script type="text/javascript">
// Other script code removed because of space limitations on stackoverflow....
$.validator.addMethod("testif", function (value, element, parameters) {
var practicing = $(parameters[0]).val(), desiredvalue = parameters[1], proflicense = value;
console.log(practicing[0].val());
if (practicing && practicing[0] === 1) {
alert(value);
return value != null;
}
});
$.validator.unobtrusive.adapters.add("testif", "desiredvalue", function (options) {
options.rules.testif = {};
options.
options.messages["testif"] = options.message;
});
</script>
}
答案1
得分: 2
我为您提供翻译好的部分:
ApplicationVM.cs
public class ApplicationVM
{
[Display(Name = "是否正在练习?")]
public int? CurrentlyPracticing { get; set; }
[Display(Name = "已获得执照")]
[AnotherTest("CurrentlyPracticing", 1, ErrorMessage = "练习时必填。")]
public string? ProfLicense { get; set; }
}
AnotherTestAttribute.cs
public class AnotherTestAttribute : ValidationAttribute, IClientModelValidator
{
private readonly string _propertyName;
private readonly object _desiredValue;
public AnotherTestAttribute(string propertyName, object desiredValue)
{
_propertyName = propertyName;
_desiredValue = desiredValue;
}
// ...(其余代码不翻译)
}
Controller
[HttpGet]
public IActionResult Index()
{
return View(new ApplicationVM());
}
[HttpPost]
public IActionResult Index(ApplicationVM applicationVM)
{
return View(applicationVM);
}
Index.cshtml
@model ApplicationVM
@{
List<SelectListItem> choices1 = new List<SelectListItem>
{
new SelectListItem { Value = "1", Text = "是" },
new SelectListItem { Value = "0", Text = "否" },
};
ViewBag.YesNo2 = choices1;
}
<div class="col-md-8 m-auto">
<h1 class="text-center">PACE Program Initial Application</h1>
</div>
<div class ="col-md-8 m-auto">@Model.ProfLicense</div>
<div class="col-md-8 m-auto">
<form asp-action="index" method="post">
<div class="form-group col-sm-5 col-md-4 col-lg-3">
<label asp-for="CurrentlyPracticing" class="control-label"></label>
<select asp-for="CurrentlyPracticing" class="form-control" id="CurrentlyPracticing" asp-items="ViewBag.YesNo2"></select>
<span asp-validation-for="CurrentlyPracticing" class="text-danger"></span>
</div>
<div class="form-group col-sm-5 col-md-4 col-lg-3">
<label asp-for="ProfLicense" class="control-label"></label>
<input asp-for="ProfLicense" class="form-control" />
<span asp-validation-for="ProfLicense" class="text-danger"></span>
</div>
<input type="submit" value="提交" class="btn btn-primary" />
</form>
</div>
@section scripts {
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
<script>
$.validator.addMethod("anothertest",
function (value, element, parameters) {
var propertyname = $(element).data('val-anothertest-propertyname');
var desiredvalue = $(element).data('val-anothertest-desiredvalue');
if ((value.trim() == "") && (document.getElementById(propertyname).value == desiredvalue)) {
return false;
}
else {
return true;
}
});
$.validator.unobtrusive.adapters.addBool("anothertest");
</script>
}
希望这能帮助您理解和使用代码。如果有其他问题,请随时提出。
英文:
I wrote a small working sample you can try reference<br>
ApplicationVM.cs
public class ApplicationVM
{
[Display(Name = "Currently Practicing?")]
public int? CurrentlyPracticing { get; set; }
[Display(Name = "State Licensed")]
[AnotherTest("CurrentlyPracticing", 1,ErrorMessage = "Required if practicing.")]
public string? ProfLicense { get; set; }
}
AnotherTestAttribute.cs
public class AnotherTestAttribute : ValidationAttribute, IClientModelValidator
{
private readonly string _propertyName;
private readonly object _desiredValue;
public AnotherTestAttribute(string propertyName, object desiredValue)
{
_propertyName = propertyName;
_desiredValue = desiredValue;
}
public void AddValidation(ClientModelValidationContext context)
{
var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-anothertest", errorMessage);
MergeAttribute(context.Attributes, "data-val-anothertest-propertyname", _propertyName);
MergeAttribute(context.Attributes, "data-val-anothertest-desiredvalue", _desiredValue.ToString());
}
private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//I didn't implement server validation
return ValidationResult.Success;
}
}
Controller
[HttpGet]
public IActionResult Index()
{
return View(new ApplicationVM());
}
[HttpPost]
public IActionResult Index(ApplicationVM applicationVM)
{
return View(applicationVM);
}
Index.cshtml
@model ApplicationVM
@{
List<SelectListItem> choices1 = new()
{
new SelectListItem { Value = "1",Text="Yes"},
new SelectListItem { Value = "0",Text="No"},
};
ViewBag.YesNo2 = choices1;
}
<div class="col-md-8 m-auto">
<h1 class="text-center">PACE Program Initial Application</h1>
</div>
<div class ="col-md-8 m-auto">@Model.ProfLicense</div>
<div class="col-md-8 m-auto">
<form asp-action="index" method="post">
<div class="form-group col-sm-5 col-md-4 col-lg-3">
<label asp-for="CurrentlyPracticing" class="control-label"></label>
<select asp-for="CurrentlyPracticing" class="form-control" id="CurrentlyPracticing" asp-items="ViewBag.YesNo2"></select>
<span asp-validation-for="CurrentlyPracticing" class="text-danger"></span>
</div>
<div class="form-group col-sm-5 col-md-4 col-lg-3">
<label asp-for="ProfLicense" class="control-label"></label>
<input asp-for="ProfLicense" class="form-control" />
<span asp-validation-for="ProfLicense" class="text-danger"></span>
</div>
<input type="submit" value="Submit" class="btn btn-primary" />
</form>
</div>
@section scripts {
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
<script>
$.validator.addMethod("anothertest",
function (value, element, parameters) {
var propertyname = $(element).data('val-anothertest-propertyname');
var desiredvalue = $(element).data('val-anothertest-desiredvalue');
if ((value.trim() == "") && (document.getElementById(propertyname).value==desiredvalue)) {
return false
}
else{
return true
}
});
$.validator.unobtrusive.adapters.addBool("anothertest");
</script>
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论