I'm implementing a custom validation attribute. This attribute does not only look at the value of the property it is applied to, but also at the value of another property. The other property is specified by its name.
I need to find a way to get the full id that the input for the other property will have in the final HTML output.
This is a simplified version of my validation attribute:
[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);
}
}
}
This demonstrates how it is used in a model class:
public class Report
{
[MyCustomValidation("Value2", ErrorMessage = "Error...")]
public string Value1 { get; set; }
public string Value2 { get; set; }
}
This is the JavaScript to make sure that the validation is also done on the client side:
$.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;
});
My viewmodel for the page/view with the form looks like this:
public MyViewModel
{
public Report MyReport { get; set; }
}
Note that I'm not using Report as my viewmodel, but rather as the type of a property in the viewmodel. This is important since this is the root of my problem...
The code in the view to show the input for Value1 is nothing strange (I'm using Razor Pages):
<div>
<label asp-for="MyReport.Value1"></label>
<input asp-for="MyReport.Value1" />
<span asp-validation-for="MyReport.Value1"></span>
</div>
And the output becomes:
<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>
So the problem is that in the HTML output I need data-val-mycustomvalidation-otherpropertyname to be "MyReport_Value2" instead of just "Value2". Otherwise the validation code won't be able to find the second HTML input (with id MyReport_Value2) and perform the validation.
I figure this must be done in the method AddValidation() in the attribute class, but how do I get the full name that the HTML input will recieve?
I'm guessing there is some way to get this by using the context parameter. I've seen examples of something like "*.TemplateInfo.GetFullHtmlFieldId(PropertyName)" but I can't get it to work.
Any help is appreciated!