ASP.NET Core MVC - Client-Side Validation for Custom Attribute

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:
Sample Image

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



Leave a reply



Submit