I am trying to write a custom validation attribute that will conditionally require fields based on boolean properties of the model.
I have my attribute implementing IClientValidatable. I have the name of the property to check, but I dont know how to get the client id of the target property.
public IEnumerable<ModelClientValidationRule>
GetClientValidationRules(ModelMetadata metadata,
ControllerContext context)
{
var clientTarget = ?????;
var rule = new ModelClientValidationRule()
{
ErrorMessage =
FormatErrorMessage(metadata.DisplayName ?? metadata.PropertyName),
ValidationType = "requiredif"
};
rule.ValidationParameters["target"] = clientTarget;
yield return rule;
}
The javascript:
$.validator.addMethod("requiredif", function (value, element, target)
{
//check on value of target
});
$.validator.unobtrusive.adapters.addSingleVal("requiredif", "target");
How can I get the client id of the target property so that the client side javascript can check on the value?
I took Nathan's excellent answer, added some comments, and wrapped it in an extension method named GetHtmlId, so that now I can use code like this to get the HTML ID of any other element on the same page:
public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule {
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredif",
};
// Find the value on the control we depend on...
string depProp = this.GetHtmlId(metadata, context, this.DependentPropertyName);
rule.ValidationParameters.Add("dependentproperty", depProp);
yield return rule;
}
And here's the extension method:
using System;
using System.Collections.Generic;
using System.Web.Mvc;
namespace sbs.Lib.Web.ValidationAttributes
{
public static class IClientValidatableExtensions
{
/// <summary> Returns the HTML ID of the specified view model property. </summary>
/// <remarks> Based on: http://stackoverflow.com/a/21018963/1637105 </remarks>
/// <param name="metadata"> The model metadata. </param>
/// <param name="viewContext"> The view context. </param>
/// <param name="propertyName"> The name of the view model property whose HTML ID is to be returned. </param>
public static string GetHtmlId(this IClientValidatable me,
ModelMetadata metadata, ControllerContext context,
string propertyName)
{
var viewContext = context as ViewContext;
if (viewContext == null || viewContext.ViewData.TemplateInfo.HtmlFieldPrefix == string.Empty) {
return propertyName;
} else {
// This is tricky. The "Field ID" returned by GetFullHtmlFieldId is the HTML ID
// attribute created by the MVC view engine for the property whose validator is
// being set up by the caller of this routine. This code removes the property
// name from the Field ID, then inserts the specified property name.
// Of course, this only works for elements on the same page as the caller of
// this routine!
string fieldId = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId("");
fieldId = fieldId.Remove(fieldId.LastIndexOf("_"));
return fieldId + "_" + propertyName;
}
}
}
}
This has only been tested with MVC5, but I suspect it hasn't changed from MVC3.
This is kind of ugly, but it seems to work. There are two assumptions:
ViewContext
for the ControllerContext
argument of GetClientValidationRules(....)
. In all my testing this has been the case, but I can't guarantee this 100%.If both of these assumptions hold true, then the following seems to work:
var viewContext = (ViewContext)context;
if(ViewData.TemplateInfo.HtmlFieldPrefix != string.Empty)
{
string fieldId = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId("");
fieldId = fieldId.Remove(fieldId.LastIndexOf("_"));
fieldId = fieldId + "_" + BooleanPropertyName
}
else
{
string fieldId = BooleanPropertyName
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With