ASP.NET MVC3 Custom Validation Message Behaviour

喜你入骨 提交于 2019-12-04 14:22:57

问题


Coming from the asp.net webforms model I'm used to using validators which display an error in the form <span title="Username is Required">*</span>.

I'm quite clear how the MVC3 validators work out of the box, so please no more answers explaining how validators work in MVC3 as I'm pretty sure I have that nailed. What I am trying to accomplish is have the validation error message showing as the title of the span tag as shown in the first paragraph.

I have managed to replicate this in MVC3 but am not sure if the way I have done it follows best practise. I would appreciate any input as to whether there is a better way to accomplish the same thing. It would be really great if it could be done without modifying jquery.validate.unobtrusive.js.

So what I have done is:

  1. Set the validation message to "*"
  2. Hidden the validation message while its valid
  3. Added a new attribute to determine whether to add the message as the title
  4. Added 2 lines of flagged code to onError to check whether to display the error message in the title, and if so to do so.
    [.cshtml]    @Html.ValidationMessageFor(m => m.Email, "*", new { data_val_usetitle = "true" })

    [.css]    .field-validation-valid {display:none;}

    .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; /* New Line */

                container.removeClass("field-validation-valid").addClass("field-validation-error");
                error.data("unobtrusiveContainer", container);

                if (replace) {
                    container.empty();
                    error.removeClass("input-validation-error").appendTo(container);
                }
                else {
                    if (useTitle) container.attr("title", error.text()); /* New Line */
                    error.hide();
                }
            }

回答1:


I think what you've done is the cleanest way. There is no way around modifying jquery.validate.unobtrusive.js because MVC extensions don't follow the old school asp.net methodology of emitting javascript on the fly.

I just finished creating my own custom validation extension calld ValidationIconFor() so that a single image is displayed with its title set to the error message and I've used a modified version of your code above.

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));
    }
}



回答2:


All of the javascript and css you posted is done for you by the validation libraries. All you need to do is add validation attributes to your model and then place the validation message and summary html/razor markup.

Model:

 public class LogOnModel
    {
        [Required]
        [Display(Name = "Username")]
        public string UserName { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }
    }

View:

                <div class="editor-label">
                    @Html.LabelFor(m => m.UserName)*
                </div>
                <div class="editor-field">
                    @Html.TextBoxFor(m => m.UserName, new { autocomplete = "off" })
                    @Html.ValidationMessageFor(m => m.UserName, "")
                </div>
                <div class="editor-label">
                    @Html.LabelFor(m => m.Password)*
                </div>
                <div class="editor-field">
                    @Html.PasswordFor(m => m.Password, new { autocomplete = "off" })
                    @Html.ValidationMessageFor(m => m.Password, "")
                </div>


来源:https://stackoverflow.com/questions/8967809/asp-net-mvc3-custom-validation-message-behaviour

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!