Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get id of model property for use with a custom IClientValidatable in MVC3

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?

like image 301
Mr Bell Avatar asked Feb 23 '23 09:02

Mr Bell


2 Answers

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;
            }
        }
    }
}
like image 112
Bob.at.Indigo.Health Avatar answered Feb 25 '23 22:02

Bob.at.Indigo.Health


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:

  1. The MVC Framework actually always passes in a ViewContext for the ControllerContext argument of GetClientValidationRules(....). In all my testing this has been the case, but I can't guarantee this 100%.
  2. The other property is on the same model class level (not a sub property of a complex type for instance)

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
 }
like image 38
Nathan Avatar answered Feb 25 '23 23:02

Nathan