ASP.Net Core MVC - Client-side validation for custom attribute
The IClientModelValidator
is in fact the right interface. I've made a contrived sample implementation below.
Attribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class CannotBeRedAttribute : ValidationAttribute, IClientModelValidator
{
public override bool IsValid(object value)
{
var message = value as string;
return message?.ToUpper() == "RED";
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val-cannotbered", errorMessage);
}
private bool MergeAttribute(
IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
Model
public class ContactModel
{
[CannotBeRed(ErrorMessage = "Red is not allowed!")]
public string Message { get; set; }
}
View
@model WebApplication.Models.ContactModel
<form asp-action="Contact" method="post">
<label asp-for="Message"></label>
<input asp-for="Message" />
<span asp-validation-for="Message"></span>
<input type="submit" value="Save" />
</form>
@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("cannotbered",
function (value, element, parameters) {
return value.toUpperCase() !== "RED";
});
$.validator.unobtrusive.adapters.add("cannotbered", [], function (options) {
options.rules.cannotbered = {};
options.messages["cannotbered"] = options.message;
});
</script>
}
asp mvc core 3 Client side validation for a custom attribute validation
In the custom validation attribute, implement the IClientModelValidator
interface and create an AddValidation
method. In the AddValidation
method, add data-
attributes for validation as follows:
public class UniqueTitleAttribute : ValidationAttribute, IClientModelValidator
{
protected override ValidationResult IsValid(
object value, ValidationContext validationContext)
{
var context = (ApplicationDbContext)validationContext.GetService(typeof(ApplicationDbContext));
var entity = context.Articles.SingleOrDefault(e => e.Title == value.ToString());
if (entity != null)
{
return new ValidationResult(GetErrorMessage(value.ToString()));
}
return ValidationResult.Success;
}
public void AddValidation(ClientModelValidationContext context)
{
Type obj = typeof(Article);
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-uniquetitle", GetErrorMessage());
}
private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
public string GetErrorMessage()
{
return $"The title is already in use.";
}
public string GetErrorMessage(string title)
{
return $"Title {title} is already in use.";
}
}
Add a method to jQuery validation library. It uses addMethod()
method to specify our own validation function. The validation function receives the value entered in the title textbox. It then performs the validation and returns a boolean value.
<div class="row">
<div class="col-md-4">
<form method="post" asp-action="CreateArticle">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Author" class="control-label"></label>
<input asp-for="Author" class="form-control" />
<span asp-validation-for="Author" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
@section Scripts
{
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script type="text/javascript">
var titlelist = @Html.Raw(Json.Serialize(ViewBag.TitleList));
$.validator.addMethod("uniquetitle",
function (value, element, param) {
if (titlelist.includes(value)) {
return false;
}
else {
return true;
}
});
$.validator.unobtrusive.adapters.addBool("uniquetitle");
</script>
}
Save the TitleList in ViewBag in the Get method of the view ,in order to judge if the title is in use from js:
public IActionResult CreateArticle()
{
ViewBag.TitleList = _context.Articles.Select(a => a.Title).ToList();
return View();
}
Result:
ASP.NET Core client side validation for custom validation attribute on razor page
How can I get the custom validation attribute to work the same way?
If you'd like to do same validation logic on client side as you did on custom server-side validation attribute on model property.
You can try to implement custom client-side validation based on your actual requirement, for more information, please refer to this doc:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-3.1#custom-client-side-validation
Besides, if possible, you can try to use the [Remote] attribute that also enables us to validate combinations of fields, which might help you achieve same requirement easily.
How to set up Client side validation for a custom RequiredIf attribute for Asp.NET Core 3.0
Make a new class inheriting ValidationAttribute and IClientModelValidator:
public class RequiredIfAttribute : ValidationAttribute, IClientModelValidator
{
private string PropertyName { get; set; }
private object DesiredValue { get; set; }
public RequiredIfAttribute(string propertyName, object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
object instance = context.ObjectInstance;
Type type = instance.GetType();
object propertyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
if ((value == null && propertyvalue == DesiredValue) || (value == null && propertyvalue != null && propertyvalue.Equals(DesiredValue)))
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val-requiredif", errorMessage);
MergeAttribute(context.Attributes, "data-val-requiredif-otherproperty", PropertyName);
MergeAttribute(context.Attributes, "data-val-requiredif-otherpropertyvalue", DesiredValue == null? "": DesiredValue.ToString());
}
private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
}
Custom client side validation attribute with parameter in ASP.NET Core using IClientModelValidator
Your MergeAttribute(..)
lines of code in the AddValidation()
method are correct and will add the data-val-*
attributes for client side validation.
Your scripts need to be
$.validator.addMethod("cannotbevalue", function(value, element, params) {
if ($(element).val() == params.targetvalue) {
return false;
}
return true;
});
$.validator.unobtrusive.adapters.add('cannotbevalue', ['value'], function(options) {
options.rules['cannotbevalue'] = { targetvalue: options.params.value };
options.messages['cannotbevalue'] = options.message;
});
Related Topics
Add the Where Clause Dynamically in Entity Framework
Returning a String from Pinvoke
Assemblytitle' Attribute in the .Net Framework
Should I Use Mkannotation, Mkannotationview or Mkpinannotation
Getters and Setters Are Bad Oo Design
How to Bind an Objective-C Static Library to Xamarin.iOS
How to Get Utc Offset in JavaScript (Analog of Timezoneinfo.Getutcoffset in C#)
C# Linq Where Clause as a Variable
How to Target Mono Framework from Vs2015
How to Create a Progress Bar with Rounded Corners in iOS Using Xamarin.Forms
Filesystemwatcher to Watch Unc Path
Deploying ASP.NET Web Forms Project to Fedora 24
Convert JavaScript Regex to C#
How to Hook into Hardware Keyboard Events at an Application Level