Я думаю, что вы сделали, это самый чистый способ. Нет никакого способа изменить jquery.validate.unobtrusive.js, потому что расширения MVC не следуют методологии asp.net старой школы, заключающейся в создании javascript на лету.
Я только что закончил создание собственного пользовательского расширения проверки Calld ValidationIconFor (), чтобы отображалось одно изображение с заголовком, установленным в сообщении об ошибке, и я использовал измененную версию вашего кода выше.
jquery.validate.unobtrusive.js
function onError(error, inputElement) { // 'this' is the form element
var container = $(this).find("[data-valmsg-for='" + inputElement[0].name + "']"),
replace = $.parseJSON(container.attr("data-valmsg-replace")) !== false,
useTitle = $.parseJSON(container.attr("data-val-usetitle")) !== false;
container.removeClass("field-validation-valid").addClass("field-validation-error");
error.data("unobtrusiveContainer", container);
if (replace) {
container.empty();
if (useTitle)
container.attr("title", error.text());
else
error.removeClass("input-validation-error").appendTo(container);
}
else {
if (useTitle)
container.attr("title", error.text());
error.hide();
}
}
ValidationExtensions.cs :
public static class ValidationExtensions
{
private static string _resourceClassKey;
public static string ResourceClassKey
{
get
{
return _resourceClassKey ?? String.Empty;
}
set
{
_resourceClassKey = value;
}
}
private static FieldValidationMetadata ApplyFieldValidationMetadata(HtmlHelper htmlHelper, ModelMetadata modelMetadata, string modelName)
{
FormContext formContext = htmlHelper.ViewContext.FormContext;
FieldValidationMetadata fieldMetadata = formContext.GetValidationMetadataForField(modelName, true /* createIfNotFound */);
// write rules to context object
IEnumerable<ModelValidator> validators = ModelValidatorProviders.Providers.GetValidators(modelMetadata, htmlHelper.ViewContext);
foreach (ModelClientValidationRule rule in validators.SelectMany(v => v.GetClientValidationRules()))
{
fieldMetadata.ValidationRules.Add(rule);
}
return fieldMetadata;
}
private static string GetInvalidPropertyValueResource(HttpContextBase httpContext)
{
string resourceValue = null;
if (!String.IsNullOrEmpty(ResourceClassKey) && (httpContext != null))
{
// If the user specified a ResourceClassKey try to load the resource they specified.
// If the class key is invalid, an exception will be thrown.
// If the class key is valid but the resource is not found, it returns null, in which
// case it will fall back to the MVC default error message.
resourceValue = httpContext.GetGlobalResourceObject(ResourceClassKey, "InvalidPropertyValue", CultureInfo.CurrentUICulture) as string;
}
return resourceValue ?? "The value '{0}' is invalid.";
}
private static string GetUserErrorMessageOrDefault(HttpContextBase httpContext, ModelError error, ModelState modelState)
{
if (!String.IsNullOrEmpty(error.ErrorMessage))
{
return error.ErrorMessage;
}
if (modelState == null)
{
return null;
}
string attemptedValue = (modelState.Value != null) ? modelState.Value.AttemptedValue : null;
return String.Format(CultureInfo.CurrentCulture, GetInvalidPropertyValueResource(httpContext), attemptedValue);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString ValidationIconFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
return ValidationIconFor(htmlHelper, expression, null /* validationMessage */, new RouteValueDictionary());
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString ValidationIconFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string validationMessage)
{
return ValidationIconFor(htmlHelper, expression, validationMessage, new RouteValueDictionary());
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString ValidationIconFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string validationMessage, object htmlAttributes)
{
return ValidationIconFor(htmlHelper, expression, validationMessage, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString ValidationIconFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string validationMessage, IDictionary<string, object> htmlAttributes)
{
return ValidationMessageHelper(htmlHelper,
ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData),
ExpressionHelper.GetExpressionText(expression),
validationMessage,
htmlAttributes);
}
[SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Normalization to lowercase is a common requirement for JavaScript and HTML values")]
private static MvcHtmlString ValidationMessageHelper(this HtmlHelper htmlHelper, ModelMetadata modelMetadata, string expression, string validationMessage, IDictionary<string, object> htmlAttributes)
{
string modelName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(expression);
FormContext formContext = htmlHelper.ViewContext.FormContext;
if (!htmlHelper.ViewData.ModelState.ContainsKey(modelName) && formContext == null)
{
return null;
}
ModelState modelState = htmlHelper.ViewData.ModelState[modelName];
ModelErrorCollection modelErrors = (modelState == null) ? null : modelState.Errors;
ModelError modelError = (((modelErrors == null) || (modelErrors.Count == 0)) ? null : modelErrors.FirstOrDefault(m => !String.IsNullOrEmpty(m.ErrorMessage)) ?? modelErrors[0]);
if (modelError == null && formContext == null)
{
return null;
}
TagBuilder builder = new TagBuilder("img");
builder.MergeAttributes(htmlAttributes);
builder.AddCssClass((modelError != null) ? HtmlHelper.ValidationMessageCssClassName : HtmlHelper.ValidationMessageValidCssClassName);
if (!String.IsNullOrEmpty(validationMessage))
{
builder.Attributes.Add("title", validationMessage);
}
else if (modelError != null)
{
builder.Attributes.Add("title", GetUserErrorMessageOrDefault(htmlHelper.ViewContext.HttpContext, modelError, modelState));
}
if (formContext != null)
{
bool replaceValidationMessageContents = String.IsNullOrEmpty(validationMessage);
if (htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
{
builder.MergeAttribute("data-valmsg-for", modelName);
builder.MergeAttribute("data-valmsg-replace", replaceValidationMessageContents.ToString().ToLowerInvariant());
builder.MergeAttribute("data-val-usetitle", "true");
}
else
{
FieldValidationMetadata fieldMetadata = ApplyFieldValidationMetadata(htmlHelper, modelMetadata, modelName);
// rules will already have been written to the metadata object
fieldMetadata.ReplaceValidationMessageContents = replaceValidationMessageContents; // only replace contents if no explicit message was specified
// client validation always requires an ID
builder.GenerateId(modelName + "_validationMessage");
fieldMetadata.ValidationMessageId = builder.Attributes["id"];
}
}
return builder.ToMvcHtmlString(TagRenderMode.Normal);
}
}
internal static class TagBuilderExtensions
{
internal static MvcHtmlString ToMvcHtmlString(this TagBuilder tagBuilder, TagRenderMode renderMode)
{
return new MvcHtmlString(tagBuilder.ToString(renderMode));
}
}