diff --git a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj index f01d713f0f..9fa659b1f2 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj +++ b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj @@ -197,6 +197,7 @@ + diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index 04bfed9fb5..db0026933e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -939,6 +939,86 @@ namespace Microsoft.AspNet.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("FilterFactoryAttribute_TypeMustImplementIFilter"), p0, p1); } + /// + /// Validation parameter names in unobtrusive client validation rules cannot be empty. Client rule type: {0} + /// + internal static string UnobtrusiveJavascript_ValidationParameterCannotBeEmpty + { + get { return GetString("UnobtrusiveJavascript_ValidationParameterCannotBeEmpty"); } + } + + /// + /// Validation parameter names in unobtrusive client validation rules cannot be empty. Client rule type: {0} + /// + internal static string FormatUnobtrusiveJavascript_ValidationParameterCannotBeEmpty(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("UnobtrusiveJavascript_ValidationParameterCannotBeEmpty"), p0); + } + + /// + /// Validation parameter names in unobtrusive client validation rules must start with a lowercase letter and consist of only lowercase letters or digits. Validation parameter name: {0}, client rule type: {1} + /// + internal static string UnobtrusiveJavascript_ValidationParameterMustBeLegal + { + get { return GetString("UnobtrusiveJavascript_ValidationParameterMustBeLegal"); } + } + + /// + /// Validation parameter names in unobtrusive client validation rules must start with a lowercase letter and consist of only lowercase letters or digits. Validation parameter name: {0}, client rule type: {1} + /// + internal static string FormatUnobtrusiveJavascript_ValidationParameterMustBeLegal(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("UnobtrusiveJavascript_ValidationParameterMustBeLegal"), p0, p1); + } + + /// + /// Validation type names in unobtrusive client validation rules cannot be empty. Client rule type: {0} + /// + internal static string UnobtrusiveJavascript_ValidationTypeCannotBeEmpty + { + get { return GetString("UnobtrusiveJavascript_ValidationTypeCannotBeEmpty"); } + } + + /// + /// Validation type names in unobtrusive client validation rules cannot be empty. Client rule type: {0} + /// + internal static string FormatUnobtrusiveJavascript_ValidationTypeCannotBeEmpty(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("UnobtrusiveJavascript_ValidationTypeCannotBeEmpty"), p0); + } + + /// + /// Validation type names in unobtrusive client validation rules must consist of only lowercase letters. Invalid name: "{0}", client rule type: {1} + /// + internal static string UnobtrusiveJavascript_ValidationTypeMustBeLegal + { + get { return GetString("UnobtrusiveJavascript_ValidationTypeMustBeLegal"); } + } + + /// + /// Validation type names in unobtrusive client validation rules must consist of only lowercase letters. Invalid name: "{0}", client rule type: {1} + /// + internal static string FormatUnobtrusiveJavascript_ValidationTypeMustBeLegal(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("UnobtrusiveJavascript_ValidationTypeMustBeLegal"), p0, p1); + } + + /// + /// Validation type names in unobtrusive client validation rules must be unique. The following validation type was seen more than once: {0} + /// + internal static string UnobtrusiveJavascript_ValidationTypeMustBeUnique + { + get { return GetString("UnobtrusiveJavascript_ValidationTypeMustBeUnique"); } + } + + /// + /// Validation type names in unobtrusive client validation rules must be unique. The following validation type was seen more than once: {0} + /// + internal static string FormatUnobtrusiveJavascript_ValidationTypeMustBeUnique(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("UnobtrusiveJavascript_ValidationTypeMustBeUnique"), p0); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs index c2e99616d0..9b3ca76b19 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs @@ -48,6 +48,7 @@ namespace Microsoft.AspNet.Mvc.Rendering private readonly IUrlHelper _urlHelper; private readonly IViewEngine _viewEngine; private readonly AntiForgery _antiForgeryInstance; + private readonly IActionBindingContextProvider _actionBindingContextProvider; private ViewContext _viewContext; @@ -57,13 +58,15 @@ namespace Microsoft.AspNet.Mvc.Rendering public HtmlHelper( [NotNull] IViewEngine viewEngine, [NotNull] IModelMetadataProvider metadataProvider, - [NotNull] IUrlHelper urlHelper, - [NotNull] AntiForgery antiForgeryInstance) + [NotNull] IUrlHelper urlHelper, + [NotNull] AntiForgery antiForgeryInstance, + [NotNull] IActionBindingContextProvider actionBindingContextProvider) { _viewEngine = viewEngine; MetadataProvider = metadataProvider; _urlHelper = urlHelper; _antiForgeryInstance = antiForgeryInstance; + _actionBindingContextProvider = actionBindingContextProvider; // Underscores are fine characters in id's. IdAttributeDotReplacement = "_"; @@ -640,8 +643,21 @@ namespace Microsoft.AspNet.Mvc.Rendering // then we can't render the attributes (we'd have no
to attach them to). protected IDictionary GetValidationAttributes(string name, ModelMetadata metadata) { - // TODO: Add validation attributes to input helpers. - return new Dictionary(); + var formContext = ViewContext.GetFormContextForClientValidation(); + if (!ViewContext.UnobtrusiveJavaScriptEnabled || formContext == null) + { + return null; + } + + var fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name); + if (formContext.RenderedField(fullName)) + { + return null; + } + + formContext.RenderedField(fullName, true); + var clientRules = GetClientValidationRules(name, metadata); + return UnobtrusiveValidationAttributesGenerator.GetValidationAttributes(clientRules); } protected virtual HtmlString GenerateCheckBox(ModelMetadata metadata, string name, bool? isChecked, @@ -1350,6 +1366,18 @@ namespace Microsoft.AspNet.Mvc.Rendering return new HtmlString(Encode(resolvedValue)); } + protected virtual IEnumerable GetClientValidationRules(string name, ModelMetadata metadata) + { + var actionBindingContext = _actionBindingContextProvider.GetActionBindingContextAsync(ViewContext).Result; + metadata = metadata ?? + ExpressionMetadataProvider.FromStringExpression(name, ViewData, MetadataProvider); + return actionBindingContext.ValidatorProviders + .SelectMany(vp => vp.GetValidators(metadata)) + .OfType() + .SelectMany(v => v.GetClientValidationRules( + new ClientModelValidationContext(metadata, MetadataProvider))); + } + private static string GetInputTypeString(InputType inputType) { switch (inputType) diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelperOfT.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelperOfT.cs index 5163c07160..5ef8228a85 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelperOfT.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelperOfT.cs @@ -33,8 +33,9 @@ namespace Microsoft.AspNet.Mvc.Rendering [NotNull] IViewEngine viewEngine, [NotNull] IModelMetadataProvider metadataProvider, [NotNull] IUrlHelper urlHelper, - [NotNull] AntiForgery antiForgeryInstance) - : base(viewEngine, metadataProvider, urlHelper, antiForgeryInstance) + [NotNull] AntiForgery antiForgeryInstance, + [NotNull] IActionBindingContextProvider actionBindingContextProvider) + : base(viewEngine, metadataProvider, urlHelper, antiForgeryInstance, actionBindingContextProvider) { } diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/UnobtrusiveValidationAttributesGenerator.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/UnobtrusiveValidationAttributesGenerator.cs new file mode 100644 index 0000000000..41db146355 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/UnobtrusiveValidationAttributesGenerator.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.Core; + +namespace Microsoft.AspNet.Mvc.Rendering +{ + public static class UnobtrusiveValidationAttributesGenerator + { + public static IDictionary GetValidationAttributes( + [NotNull] IEnumerable clientRules) + { + IDictionary results = null; + + foreach (var rule in clientRules) + { + if (results == null) + { + results = new Dictionary(StringComparer.Ordinal); + } + + var ruleName = "data-val-" + rule.ValidationType; + + ValidateUnobtrusiveValidationRule(rule, results, ruleName); + + results.Add(ruleName, rule.ErrorMessage ?? string.Empty); + ruleName += "-"; + + foreach (var kvp in rule.ValidationParameters) + { + results.Add(ruleName + kvp.Key, kvp.Value ?? string.Empty); + } + } + + if (results != null) + { + results.Add("data-val", "true"); + } + + return results; + } + + private static void ValidateUnobtrusiveValidationRule(ModelClientValidationRule rule, + IDictionary resultsDictionary, string dictionaryKey) + { + if (string.IsNullOrEmpty(rule.ValidationType)) + { + throw new ArgumentException( + Resources.FormatUnobtrusiveJavascript_ValidationTypeCannotBeEmpty(rule.GetType().FullName), "rule"); + } + + if (resultsDictionary.ContainsKey(dictionaryKey)) + { + throw new InvalidOperationException( + Resources.FormatUnobtrusiveJavascript_ValidationTypeMustBeUnique(rule.ValidationType)); + } + + if (!rule.ValidationType.All(char.IsLower)) + { + throw new InvalidOperationException( + Resources.FormatUnobtrusiveJavascript_ValidationTypeMustBeLegal( + rule.ValidationType, + rule.GetType().FullName)); + } + + foreach (var key in rule.ValidationParameters.Keys) + { + if (string.IsNullOrEmpty(key)) + { + throw new InvalidOperationException( + Resources.FormatUnobtrusiveJavascript_ValidationParameterCannotBeEmpty(rule.GetType().FullName)); + } + + if (!char.IsLower(key[0]) || key.Any(c => !char.IsLower(c) && !char.IsDigit(c))) + { + throw new InvalidOperationException( + Resources.FormatUnobtrusiveJavascript_ValidationParameterMustBeLegal( + key, + rule.GetType().FullName)); + } + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index 92c39ca690..a0a50aef3b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -288,4 +288,19 @@ The type provided to '{0}' must implement '{1}'. - + + Validation parameter names in unobtrusive client validation rules cannot be empty. Client rule type: {0} + + + Validation parameter names in unobtrusive client validation rules must start with a lowercase letter and consist of only lowercase letters or digits. Validation parameter name: {0}, client rule type: {1} + + + Validation type names in unobtrusive client validation rules cannot be empty. Client rule type: {0} + + + Validation type names in unobtrusive client validation rules must consist of only lowercase letters. Invalid name: "{0}", client rule type: {1} + + + Validation type names in unobtrusive client validation rules must be unique. The following validation type was seen more than once: {0} + + \ No newline at end of file