I spent the afternoon playing around with the source code some more, and discovered several key helper methods that allowed me to do what I needed to do.
It's not the prettiest thing in the world, but it goes a long way towards automating what I was doing before. My solution was to create a new TextBoxFor helper method called TextBoxForChild:
public static MvcHtmlString TextBoxForChild<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
string parentName,
int index,
IDictionary<string, object> htmlAttributes)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var rules = ModelValidatorProviders.Providers.GetValidators(metadata, htmlHelper.ViewContext).SelectMany(v => v.GetClientValidationRules());
if (htmlAttributes == null)
htmlAttributes = new Dictionary<String, object>();
if (!String.IsNullOrWhiteSpace(metadata.Watermark))
htmlAttributes["placeholder"] = metadata.Watermark;
UnobtrusiveValidationAttributesGenerator.GetValidationAttributes(rules, htmlAttributes);
return htmlHelper.TextBox(String.Format("{0}[{1}].{2}", parentName, index, metadata.PropertyName), metadata.Model, htmlAttributes);
}
I get the "ModelMetadata" object, then I generate the "model validator rules" for the model. Then I fill in my custom "placeholder" html attribute with the watermark metadata value, and finally call "UnobtrusiveValidationAttributesGenerator.GetValidationAttributes", which fills my html attributes dictionary with all of the validation attributes.
I then do some custom string formatting to make sure the input name follows the MVC child entity format, and voila, it works!
FYI in case anyone was wondering where that "Watermark" value comes from, it's a value from "DisplayAttribute", called "Prompt":
public class FamilyMember
{
public int ClubNumber { get; set; }
[Display(Name="Name", Prompt="Name")]
[Required]
public string Name { get; set; }
public string Cell { get; set; }
public string Email1 { get; set; }
public string Email2 { get; set; }
public DateTime? BirthDate { get; set; }
}
Lovely!